Rand Protocol
文档
核心概念

共识与最终性

节点所运行的链式 HotStuff——法定人数、领导者、连续三个视图的提交规则、超时、纪元——以及“最终确定”对 testnet 上的客户端意味着什么。

使用本页可以了解交易何时最终确认、需要多少验证者在线,以及 RPC 与浏览器中所见区块字段的含义。共识为链式 HotStuff BFT,采用按质押加权的法定人数;在 chain 21 上,与 chain 20 相同,即 26 个验证者中的 18 个。

节点执行的规则

规则实现方式
法定人数法定人数证书(QC)是由持有总质押量严格超过 2/3 的验证者所签的一组 Dilithium2 投票
领导者调度纯轮询:视图 v 的领导者为 validators[v mod n],验证者集合按地址排序。质押量只为投票加权,不影响领导者的选择
提议视图 v 的领导者在持有 v − 1 的 QC,或持有针对 v 的法定人数 NewView 消息后提出提议。若提议中含有无效交易,则丢弃该交易,而不是丢弃整个区块
投票与安全性验证者每个视图只投一次票,且只为相对于其锁定而言安全的区块投票(其 justify QC 比锁定的 QC 更新,或该区块扩展了被锁定的区块)。安全状态在投票离开节点之前已 fsync 到 RocksDB
签名域在 consensus_domain: 1 的创世配置上(chain 21,以及自 chain 15 起的每条链),每一个投票、new-view 和提议的签名都覆盖创世哈希(rand-vote-2、rand-newview-2、rand-block-2),因此为某条链生成的签名无法在其他任何链上通过验证。Chain 14 及更早的链签名时不含该域。自 chain 20 起,binding_domain: 1 将同一规则扩展到交易证明和已签名的操作(交易)
同级提议副本对每个(视图,领导者)只保留一个区块;该领导者在同一视图中提出的第二个不同区块,会作为双重提议(equivocation)被拒绝。比副本自身视图超前 8 个视图以上的提议(PROPOSAL_VIEW_WINDOW)在检查其签名之前即被拒绝;推测树已满时,会先逐出不在主分支上的区块,再拒绝任何提议
锁释放锁绝不会被自动降低(CON-4,v0.6.8)。它只会让位于 justify QC 级别高于它的提议,或让位于被锁定区块的到达。来自法定人数的已签名 NotHeld 应答会记录给运维人员,但不释放任何锁;验证者会记录自己的投票,因此绝不会对自己已投票的区块签署 NotHeld。每个高于头部的已认证区块都会被持久化,并在重启时恢复,因此整个集群重启不会让 QC 滞留在无人持有的区块上。被锁定区块永久丢失的验证者,通过离线手动释放:rand-node safety release-lock
QC 组装每个验证者根据经 gossip 传播的投票在本地组装 QC,因此即使下一任领导者离线,链仍可继续提交
提交链式三 QC 规则:当三个证书在连续视图 v, v+1, v+2 中链接在某个区块之上,且该链可回溯至已提交的头部时,该区块即被提交
超时与视图切换基础 --view-timeout-ms 为 3,000 ms,每连续超时一次翻倍,任何进展都会将其重置。超时证书步调器(CH-1,v0.6.8):计时器触发的副本停留在当前视图,并签署一条请求下一视图的 NewView。它在收到前一视图的 QC,或持有超过 2/3 质押量的验证者已请求该视图或更后的视图时进入该视图;当超过 1/3 的验证者请求比自身视图更靠后的视图时,它会加入(签署自己的请求)。任何单个验证者的 NewView 都不能使其他节点切换视图。视图有上界(MAX_VIEW_AHEAD = 10⁶)
出块节奏当没有待处理交易时,领导者提出空块,时间至少在任一提议者最近一次出块之后 --block-interval-ms(默认 1,000 ms)
投票与 gossip 限制超前 8 个视图以上的投票被忽略,每个(视图,投票者)只计一票;共识 gossip 按转发对等节点限流(突发 256,64/s);父块未知的提议,仅当其提议者在当前纪元的集合或下一纪元的集合中领导该视图、该提议在已提交头部的树范围之内、且其大小与交易根校验通过时,才作为孤块保留(孤块池上限为 64 MiB,自 v0.6.1 起每个提议者最多 16 个孤块)。自 v0.6.8 起,仅当提议的交易列表哈希到其区块头所承诺的根且无重复交易时,才会转发该提议并保留孤块(GOSSIP-2)
区块限制链的 max_block_bytes 交易字节数(默认 4 MiB;chain 21 的创世配置中为 19 MiB,即 19,922,944 字节;chain 20 中为 20 MiB)以及 2,000 笔交易,由提议者执行,并由每个副本在 Ledger::apply_block 中再次执行
紧凑区块(v0.8.0,仅节点)领导者通过 gossip 发送 CompactProposal(共识主题上的标签 4):区块头、其签名和交易哈希,在 26 个验证者和 2,000 个哈希时为 166,416 字节,而不是完整区块。副本预先筛查区块头,根据其内存池和最近见过的交易缓存重建区块,并仅按哈希获取所缺少的交易体(SyncRequest::Transactions,每次请求 512 个;rand_status.compact_fetched 对其计数)。旧版节点无法解码该帧,因此此变更随 chain 21 切换一并推出;混合版本的验证者集合将停止提交
脱离循环的应用(v0.8.0,仅节点)跟随者在阻塞线程上应用收到的提议(同时最多 4 个,最多 8 个等待,每个提议者最多 2 个),预检查和插入仍留在共识循环中;领导者针对同一视图的第二个区块排在第一个之后等待,第一个插入后即作为双重提议被拒绝。rand-node run --apply-proposals-inline 可恢复内联应用
区块时间在带有 bridge 部分的链上:区块的 timestamp_ms 不得早于其父块(TimestampRewind),也不得比父块晚超过 60 s(TimestampLeap);验证者对比自身时钟超前 15 s 以上的区块不投票;见下文

三个连续视图的提交规则是承重结构:放宽它会重新引入冲突最终性的缺陷,fullnode 为此设有回归测试。客户端通过 RPC 看到的一切都已提交:只提供已提交的区块,WebSocket 的 newHeads 流对每个已提交区块按顺序发送一条通知。

该表对应 v0.8.0(c0878c3f),即 chain 21 自 2026-10-12 起运行的版本;其中两个 v0.8.0 的行是仅节点的变更,不改变任何投票和提交规则,其余内容为 v0.6.8 带给 chain 20 的变更。其中三条规则是 v0.6.8(审计 v6)新增的,自 chain 20 于 2026-10-01 上线起生效:超时证书步调器(CH-1)、不自动释放锁(CON-4)以及提议交易列表检查(GOSSIP-2)。在 chain 18 和 chain 19(v0.6.7)上,单个验证者签署的 NewView 就能使每个副本切换到其视图,而锁则会在持有法定人数质押量的验证者签署 NotHeld 应答时被降低。步调器和锁规则改变了验证者签署的内容与时机,因此 v0.6.8 随 chain 20 切换一次性全部停止、全部启动,绝不逐节点滚动升级。

测试网的数字

Chain 21 有 26 个验证者,每个恰好质押 1,000 RAND,全部 26 个都在其创世配置中,使用 chain 17 至 20 所运行的密钥和 peer id。Chain 15 的创世配置中有 18 个,使用 chain 14 的密钥,另外 8 个于 2026-09-27 以 v2 注册方式绑定质押,并自第 43 纪元起生效。质押量是均匀的,因此法定人数就是人头数:26 个中必须有 18 个在线才能提交任何内容。只有 17 个在线时,视图会推进,但没有任何内容被最终确认。由 n 个等额验证者组成的集合需要超过 2/3 的质押量:2 个中的 2 个、4 个中的 3 个、6 个中的 5 个、18 个中的 13 个、26 个中的 18 个。创世配置的权重上限(每个验证者 3,333 bps)对等额质押的集合不起约束作用。

出块节奏为默认的 1,000 ms。在 chain 15 上,实际约每 1.2 s 出一个块(前 29 小时平均 1.18 s);在 chain 20 上,浏览器在高度 3,270 时报告平均 1.34 s(2026-10-01,avg_block_time_ms),在 chain 21 上,高度 2,283 时为 1.17 s(2026-10-12)。浏览器报告其观察到的平均出块时间(GET /stats 中的 avg_block_time_ms)。每当视图超时,验证者的视图就会领先于高度,这就是 rand_getHead 同时报告两者的原因。

纪元

验证者集合每隔 epoch_blocks = 1,000 个区块从登记册中重新推导一次(创世参数)。epoch(h) = h / 1000;纪元 e ≥ 1 的集合取自纪元 e − 1 最后一个区块时的登记册:所有 stake ≥ 1,000 RAND 的行,按质押量取前 100 名,再按地址排序。在没有 staking 部分的链上,质押在下一个边界即成为权重。在 chain 20 上,质押还需再等待两个纪元,并受每纪元准入额度限制:额度为边界处活跃质押权重的 2,500 bps(当集合持有 26,000 时为 6,500 RAND);若某笔质押要登记新的验证者,则除非集合已先通过 AdmitValidator 投票接纳该密钥(staking.admission_by_vote),否则会被拒绝。Chain 19 的额度是固定的每纪元 10,000 RAND,且没有准入投票。解除质押在下一个边界就会减少权重,而相应金额需再等待两个纪元才可提取。rand_getEpoch 返回当前纪元、纪元长度以及对下一个集合的预测。参见 质押。

区块包含什么

{
  "hash": "647b…", "height": 50, "view": 92, "parent": "2d41…",
  "proposer": "3v3VBJ…", "timestamp_ms": 1788000123456,
  "tx_root": "0000…", "state_root": "a1b2…", "justify_view": 91,
  "tx_count": 0, "transactions": []
}

view 是该区块被提议时所在的视图,justify_view 是它为其父区块所携带的 QC 的视图。timestamp_ms 由提议者设定,须遵守下一节的界限。state_root 承诺区块之后的账本状态。在 chain 21 上(即 chain 20 的字段,并按下文方式并入永续合约与多签状态):

inner      = blake3("rand-state-8" || tree_root || nullifier_root || validators_root || programs_root
                                   || bridge_root || tokens_root
                                   || faucet_epoch || faucet_minted_in_epoch || bond_queue_root
                                   || gas_price || byte_price
                                   || program_state_root)
state_root = blake3("rand-state-admitted-1" || inner || admitted_root)

Chain 19 承诺的字段相同,但没有 program_state_root,使用 rand-state-7,且没有外层封装。

各组成部分取决于创世配置。没有任何可选部分的链,以 rand-state-2 承诺前四项。bridge 部分会追加 bridge_root。tokens 部分在其后追加 tokens_root,并将整个哈希的域重设为 rand-state-4;带有 bridge 部分的创世必须同时带有 tokens 部分,因此 chain 21 与 chain 20 一样两者皆有。aggregation 部分接着追加聚合者根(在没有代币的链上使用 rand-state-3);chain 21 没有该部分。staking 部分最后追加水龙头的两个纪元计数器以及质押队列的根(rand-bond-queue-1),并将整个哈希的域重设为 rand-state-5,无论其他部分是否启用;chain 14 没有这一部分,使用 rand-state-4 哈希,chain 15 至 17 使用 rand-state-5 哈希。vesting 部分在其后追加归属登记册的根,并将哈希的域重设为 rand-state-6;chain 21 没有该部分。带有 dynamic 控制器的 gas 部分,在其他所有内容之后追加实时的 gas_price 和 byte_price(各为大端序 u64),并将哈希的域重设为 rand-state-7。那是 chain 19 的域,而 chain 20 与 chain 21 将价格保留在其根中:价格每个区块变动一次,发生在该区块的交易之后、根之前(费用)。价格固定的 gas 部分不在根中。program_state 部分(RPL-2)在价格之后追加每个程序的单元与保险库的根,并将哈希的域重设为 rand-state-8:这是 chain 20 的。perps 部分(RPL-3,v0.8.0)在其后追加交易所的状态,并将哈希的域重设为 rand-state-9:这是 chain 21 的;其 multisig 部分再以 rand-state-multisig-1 为域,对多签账户登记册额外封装一层(而 fees.usd_subsidy 部分,chain 21 没有,则会在 rand-state-price-1 之下加入经投票确定的价格)。Audit v6 的质押状态是折叠在外围而非内部:在 staking.admission_by_vote 之下(chain 20 和 21),根为 blake3("rand-state-admitted-1" || inner || admitted_root),而在 staking.slashing 之下(目前尚无链启用),第二层封装会加入被监禁集合。tokens_root 是以 blake3("rand-token-leaf-1" || bincode(TokenInfo)) 为叶子、按索引顺序排列的 Merkle 根,因此每个代币的元数据、权限、mint_nonce、供应量以及每种支撑资产的 locked 数额都属于共识状态:两个节点只要对某一个代币行存在分歧,就是对链存在分歧。在 chain 20 上,代币根还在 rand-token-registry-3 之下折入注册表扩展(max_tokens 上限、被销毁的注册费、票据价值界限以及桥的滚动铸造上限窗口)。桥的根涵盖发行方、守护者集合、已使用的证明摘要、销毁序号、PQ 守护者密钥、暂停密钥、暂停标志以及两个治理 nonce;资产注册表已从中移入 tokens_root。Chain 20 的桥启用了 rules_v2,因此追加了轮换 nonce 与全局上限参数,域为 rand-bridge-state-5(chain 14 的是 rand-bridge-state-4);该根随后由入站重放下限(rand-bridge-replay-floor-1)封装,在 chain 20 上还由轮换规则与待处理轮换(rand-bridge-rotation-1)以及费用组(rand-bridge-fees-1)封装。验证者登记册被哈希为 validators_root(在 chain 20 上叶子域为 rand-validator-leaf-4,并包含每行的 activation_epoch;在没有 staking 部分的链上为 rand-validator-leaf-2),因此节点只要对登记册的某一行存在分歧,就是对链存在分歧。

区块时间有界 (B2)

在有桥的链上,区块时间戳决定守护者集合的过期时间,以及某种支撑资产的每日铸造上限按哪一天计算,因此若领导者能将时钟向前拨动,就可能提前让已轮换集合的宽限期过期,或跳过某一天的上限。Chain 21 与 chain 20 一样带有 bridge 部分,因此适用三条规则,均以它为前提(perps 部分同样以此方式限定区块时间):

规则界限类型
不得回退timestamp_ms ≥ parent.timestamp_ms(允许相等),否则为 BlockError::TimestampRewind;提议者输出 max(now, parent.timestamp_ms)有效性规则,重放与同步时同样检查
不得跳跃timestamp_ms ≤ parent.timestamp_ms + MAX_TIMESTAMP_STEP_MS(60,000 ms),否则为 BlockError::TimestampLeap有效性规则,重放与同步时同样检查
时钟漂移验证者拒绝为领先自身时钟超过 MAX_CLOCK_DRIFT_MS(15,000 ms)的区块投票仅为投票规则:该区块仍留在推测树中,时钟一致的法定多数仍可对其认证,而重放已提交的历史时从不应用此规则

有桥的链上,每个验证者都需要 NTP。 时钟偏差超过 15 秒的验证者,既是有问题的领导者(其提议可能得不到投票),也是有问题的投票者(它可能拒绝诚实领导者按时提出的区块)。只要诚实多数的时钟一致,二者都不会使链停摆,但未同步的节点群会白白损失活性裕量。

创世 timestamp_ms 必须接近真实的启动时间。若过早,区块时间会落后于挂钟时间,直到步长界限允许其追上,每个区块最多追赶 60 秒。若晚于现在超过 15 秒,则区块 1 根本无法提交:每一张投票,包括领导者自己的,都会被扣留,直到挂钟时间到达该时刻。长时间停摆之后,同样的追赶可使铸造上限的计日翻转快于每个真实日一次,上限为每累积一整天的滞后多出一天的额度。

客户端视角的最终性

  • 当 rand_getTransaction(hash) 返回一个区块时,交易即为最终确定。 rand_sendTransaction 的接受仅表示进入了内存池。
  • 由 RPC 提供或由浏览器索引的区块即已提交。 没有需要等待的最终性窗口;浏览器的指南也是这样说的。
  • 来自 rand_subscribe ["newHeads"] 的区块头,是节点不会丢失的区块头:区块提交到存储之前不会发送任何内容。
  • 落后的节点会重新验证一切:在接受同步来的区块之前,验证每一份证书、每一个捆绑包和调用证明、每一张回执。终局性签名从不被用来代替证明。

恢复速查表,摘自全节点自身的说明:高度不增长且 peer_count 很低,是网络问题;高度不增长而对等节点正常,意味着在线的质押不足 2/3;启动时出现 CORRUPT CHAIN,意味着节点已截断受损的尾部,正在重新同步。

最近一次核对所依据的 fullnode 提交为 c0878c3f(链 21),日期为 2026-10-12。 若代码与本页不一致,以代码为准;请告知我们。