使用本页可以加入运行中链的验证者集合、退出该集合,以及读取登记册。共识权重必须公开,因此验证者登记册是这条链上唯一以明文记录金额的表;质押仍然来自一张屏蔽票据,也会回到一张屏蔽票据中。
登记册
每个曾经质押的验证者对应一行,以其 base58 地址为键:
ValidatorEntry {
public_key, // Dilithium2: signs blocks, and this validator's staking actions
stake: u64, // bonded: the weight an epoch's set gives it
pending: Vec<(u64, u64)>, // unbonding, as (release_epoch, amount), oldest first
rewards: u64, // fees earned as a block proposer, unpaid
payout: ShieldedAddress, // rand1… — where a withdraw pays
nonce: u64, // replay protection for its signed actions
activation_epoch: u64, // under a `staking` section: the first epoch this row may be in a set
}
每个字段都是公开的,登记册会被哈希进每个区块的状态根。行永远不会被删除:解除全部质押的验证者仍保留一行,其 stake = 0。
| 常量 | 值 | 含义 |
|---|---|---|
MIN_STAKE | 1,000 RAND | 一行要进入某个纪元的验证者集合所需的质押 |
MAX_VALIDATORS | 100 | 一个纪元的集合上限 |
UNBONDING_EPOCHS | 2 | 已解除质押的金额在可提取前需等待的纪元数 |
epoch_blocks | chain 21 上为 1,000,与 chain 20 相同 | 每个纪元的区块数;创世参数 |
纪元
epoch(h) = h / epoch_blocks。纪元 e ≥ 1 的集合由纪元 e − 1 最后一个区块时的登记册推导而来:取所有满足 stake ≥ MIN_STAKE 的行,按 (stake 降序, address 升序) 取前 MAX_VALIDATORS 个,再按地址排序。视图的领导者、区块的投票者以及证书的验证,都使用该区块所属纪元的集合。由此得到两点结果:
- 质押在之后的某个边界之前不构成权重。 它立即进入登记册,但集合只在边界处变化——没有
staking部分的链上是下一个边界,chain 20 和 chain 21 上则更晚(见下文)。 - 解除质押会在下一个边界使权重减少,而该金额要等待两个纪元才可提取。验证者在可以提取之前很久就已停止出块。
如果推导结果为空,则沿用上一个集合。以 --validator 启动、其密钥不在任何当前集合中的节点会处于观察状态,直到某个纪元接纳它,因此在创世之后才质押的验证者无需重启;rand_status 分别报告 is_validator(密钥在此)和 active_validator(它在当前集合中)。
staking 部分
创世文件可以带有 staking 部分(fullnode v0.5.4,audit v4 STAKE-2)。没有它时,上述规则就是全部,如 chain 14。自 chain 15 起的每条链都带有它。Chain 21 的该部分与 chain 20 相同,启用了:
"staking": {
"faucet_budget_per_epoch": "10000000000000", "bond_activation_epochs": 2,
"max_weight_bps": 3333, "max_stake_entry_bps_per_epoch": 2500,
"admission_by_vote": true, "registration_v2": true,
"faucet_recipients": ["rand1…", "…16 wallets…"],
"faucet_minters": ["<validator address>", "…18 validators…"]
}
| 字段 | chain 21(与 chain 20 相同) | 规则 |
|---|---|---|
bond_activation_epochs | 2 | 在纪元 e 中生效的质押排入纪元 e + 1 + 2;注册和追加质押均如此。其金额立即计入 stake,但在此之前不构成权重,且排队中的质押不能解除 |
max_stake_entry_bps_per_epoch | 2,500(25 %) | 一个边界处可成为权重的质押上限(注册与追加质押合计),以该处已有效权重的比例计:⌊active · bps / 10,000⌋。超出部分的到期行等待下一个边界,按质押顺序处理。在 chain 21 的 26,000 RAND 有效质押上,即每个边界 6,500 RAND。无论规模如何,新进入者在 k 个边界之后的份额至多为 1 − 1.25^−k,两个边界后约为三分之一 |
admission_by_vote | true | 注册新密钥的质押会被拒绝(NotAdmitted),直到持有超过三分之二投票权重的验证者投票接纳该密钥(见下文);追加质押无需投票 |
max_weight_bps | 3,333 | 任何验证者的权重都不超过其集合总权重的 33.33 %;超出部分在集合中被截断,登记册的 stake 不变 |
registration_v2 | true | 注册需对创世哈希、链 id、验证者地址和收款地址签署 rand-register-2;v1 注册会被拒绝 |
faucet_budget_per_epoch | 10,000 RAND | faucet 每个纪元最多铸造此数额(超出则 FaucetBudgetExhausted;计数器在下一个纪元重新开始) |
faucet_recipients | 16 个钱包 | faucet 的 Mint 只能向这些钱包付款(FaucetRecipientNotAllowed);正是这份名单使 faucet 能与桥并存 |
faucet_minters | 18 个验证者 | faucet 的 Mint 只能由这些验证者密钥签署(MinterNotAllowed),在准入时和区块应用时检查,无论另一把密钥已质押多久。在 chain 21 上,与 chain 20 一样,它们是 26 个创世验证者中的 18 个。自 chain 16 起,每份创世文件都带有这份名单 |
slashing | 无 | 没有 slashing 部分,因此没有验证者被罚没或监禁(公开的内容) |
在该部分之下,faucet 计数器和质押队列被附加到状态根中,域为 rand-state-5,验证者叶子为 rand-validator-leaf-4。Chain 20 和 chain 21 还有动态 gas 价格和 RPL-2 程序状态,附加在它们之后,使域变为 rand-state-8(chain 21 的 perps 状态使其进一步变为 rand-state-9,其多签登记册则包裹在 rand-state-multisig-1 之下;共识),而投票准入会把该根再包裹一次,即 H("rand-state-admitted-1", root ‖ admitted_root)。Chain 15 没有 faucet_minters 名单:在那里,每个节点只有在创世验证者签署时才会将 faucet 的 Mint 放入内存池并转发(MinterNotAllowed,属节点策略),因此创世之后才质押的验证者无法通过内存池耗尽预算。有了这份名单,同样的拒绝就成为一条有效性规则。
历史:chain 15 至 chain 19 没有 admission_by_vote,因此任何持有 1,000 RAND 的密钥都可以注册,并且它们把入场预算设为固定的 max_stake_entry_per_epoch,即每个边界 10,000 RAND。固定数额是针对某一份创世文件确定的;而比例则无论质押多少,都使新进入者的份额保持一致。这两个字段不能同时设置。
投票准入
新增于 fullnode v0.6.8(audit v6,STAKE-2),并在 chain 20 和 chain 21 上启用。以 26 把各 1,000 RAND 的密钥计,集合的阻断性三分之一约为 13,000 RAND 的购入质押,而且没有任何罚没。在此规则下,由集合决定哪些新密钥可以注册。
- 投票是一个 action,即
AdmitValidator { candidate, signatures },无捆绑且免手续费。每个签名是某个验证者对blake3("rand-admit-validator-1" ‖ genesis hash ‖ candidate address)的 Dilithium2 签名,按投票者地址升序排列。投票者必须持有投票集合权重的严格多于三分之二,与法定人数证书的算法相同:在 chain 21 上,与 chain 20 一样,是 26 个验证者中的 18 个。投票绑定于某一份创世文件,不需要 nonce:一次准入会被它所许可的注册所消耗。 - 投票集合是登记册为当前纪元所做的推导。排队中的质押在其中不构成权重,而解除质押会立即使其退出。候选者不能给自己投票。
- 已准入集合是共识状态,最多 256 把等待注册的密钥(超出则
AdmissionSetFull)。rand_getAdmitted提供该集合;rand_getLimits.admission_by_vote表明一条链是否需要投票。
# each voting validator, offline
rand-node admit sign --candidate <registration hex or public key> --key node.key.json --genesis-hash <hash>
# whoever collects the lines
rand-node admit submit --candidate <…> --signature <line> --signature <line> … --rpc http://127.0.0.1:8545
之后质押按下文继续进行。rand_getGenesisHash 返回 admit sign 所需的哈希。
四个命令
哪个二进制文件负责哪个 action,取决于由哪把密钥签名:质押会花费票据,所以它是钱包命令;解除质押和提取由验证者的 Dilithium2 密钥签名,而该密钥只有节点持有。
| action | 命令 | 载体 | 支付 |
|---|---|---|---|
| register | rand-node register | 无——输出一个 blob | 无 |
Bond | rand bond | 一个 burn_r 即为质押额的捆绑 | 从钱包的票据中支付 0.001 RAND |
Unbond | rand-node unbond | 无(bundle: null) | 无 |
Withdraw | rand-node withdraw | 无(bundle: null) | 从提取的金额中支付 0.001 RAND |
注册
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2 [--rpc http://127.0.0.1:8545]
输出验证者的地址和十六进制的 Registration:其公钥、收款地址和一个签名。使用 --v2(chain 20 和 chain 21 要求)时,签名是对链的创世哈希、链 id、验证者地址和收款地址签署的 rand-register-2;不使用时则为 v1 形式 blake3("rand-register" ‖ bincode(chain_id, payout)),它们会拒绝这种形式(在 binding_domain: 1 的链上,如 chain 20,注册消息无论如何都是 v2 形式)。RPC 会被读取以获得链 id,并在使用 --v2 时获得创世哈希,因此为一条链签署的注册在另一条链上会被拒绝。收款地址是钱包地址,而不是节点密钥,并且它是之后的追加质押唯一无法更改的字段。把该十六进制交给持有质押的人。
质押
rand bond <validator base58> <amount in RAND> [--registration <hex>] [--fee <RAND>] [--no-wait] [--cuda]
普通的屏蔽交易:钱包为每笔交易自 chain 14 起携带的 4 进 4 出隐藏资产 bundle 生成证明,质押金额记在其 RAND 销毁量 burn_r 中,账本仅在 burn_r == amount 时才接纳一笔质押。在 chain 21 上,bundle 携带两份证明,自 chain 17 起每个 bundle 均如此:tier-14 的 bundle 证明,以及针对花费密钥的 tier-10 授权证明。两者默认均在本地生成,在 Apple Silicon 笔记本上约需两分钟(证明内存)。使用钱包的 --prover 选项时,bundle 证明改由配对的证明者生成,授权证明仍留在钱包所在的机器上。--registration 恰在验证者尚未进入注册表时才需要,已在注册表中则被拒绝;钱包会先查询注册表。在 chain 20 上,除非验证者集合已先投票通过该密钥,否则注册性质的质押会被以 NotAdmitted 拒绝(投票准入);追加质押则无需投票。注册时至少质押 1,000 RAND;追加质押可以是任何大于零的金额。链所获知的只是:这个验证者的质押增加了这么多;哪些 note 支付了、它们属于谁,则与转账中一样被隐藏——任何人都可以向某个验证者质押,而除金额外不泄露任何信息。
submitted bond 8c3f…e1
0 RAND out, 1000 RAND burned, 0.999 RAND change, fee 0.001 RAND, anchored at height 412
2nRdFC…: stake 1000 RAND, counting as consensus weight from epoch 69
钱包打印出的 epoch 是下一个边界。它不计入激活延迟:在 chain 21 上,与 chain 20 一样,在 epoch 68 提交的质押最早自 epoch 71 起计入权重;若入场额度(每个边界为活跃权重的四分之一)已用尽,则更晚。
解除质押
rand-node unbond <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
将 amount 从 stake 移入 pending,其 release_epoch = epoch + 2,并使 nonce 加一。免费、不含 bundle;在 chain 14 至 19 上,签名针对 blake3("rand-unbond" ‖ bincode(chain_id, validator, amount, nonce)),自 chain 20 起则针对 blake3("rand-unbond-2" ‖ bincode(genesis_hash, chain_id, validator, amount, nonce))(创世 binding_domain: 1);它在一个区块时间内提交。rand-node v0.6.8 会根据所连接的链选择相应形式。解除质押的数额超过质押或为零时会被拒绝;同一 epoch 内的多次解除质押合并为一行 pending。
提取
rand-node withdraw <amount in RAND> --key node.key.json [--rpc …] [--no-wait]
将已释放的价值——rewards 加上 release_epoch 已到的 pending 行,按时间由旧到新——支付到注册表中 payout 地址的一个存入 note。它由验证者签名(自 chain 20 起使用 rand-withdraw-2,与解除质押一样先对创世哈希签名),不含 bundle,但并非免费:0.001 RAND 的基础费用从该金额中扣除,归打包该交易的区块的提议者,因此提取 1,000 RAND 会生成价值 999.999 的 note,而金额不足以支付基础费用的提取会被拒绝。note 由链自行计算:
cm = H_CM(payout.pk, from = 0, amount − 0.001 RAND, asset 0, time, r)
time 是命令运行时的头部高度(会被打印,并受签名约束),在 bundle 窗口内的任何位置均被接纳(chain 20 和 21 为 1,024 个区块,由其创世的 proof_window_blocks 设定;更早的链为 256);r 被公开,因此链永远不会从线路上接收承诺。之后,收款钱包通过扫描找到该 note,无需被告知任何信息:
rand --key payout.key.json balance # balance: 999.999 RAND
加入与退出
# on the joining validator's machine
rand-node register --key node.key.json --payout "$(rand --key payout.key.json address)" --v2
rand-node run --datadir ./data --key node.key.json --validator --bootstrap /ip4/…/p2p/…
# it syncs and observes; rand_status says is_validator true, active_validator false
# since chain 20: validators holding more than two thirds of the weight vote the key in
rand-node admit sign --candidate <hex from above> --key <their node.key.json> --genesis-hash <hash>
rand-node admit submit --candidate <hex from above> --signature <line> … --rpc http://127.0.0.1:8545
# on the machine holding the stake
rand bond <validator base58> 1000 --registration <hex>
rand validators # the new row, active false until it activates
# from its activation epoch on (on chain 21, as on chain 20, three boundaries after the bond at the earliest)
rand-node status # active_validator: true
rand-node unbond 1000 --key node.key.json # out of the set at the next boundary
# wait two epochs
rand-node withdraw 1000 --key node.key.json # a note worth 999.999 RAND at the payout address
解除质押之前请先核对法定人数的算术:由 n 个验证者组成的集合需要超过 2/3 的质押在线。在 chain 21 上,与 chain 20 一样,全部 26 个验证者都恰好处于 MIN_STAKE,因此任何一次解除质押都会使该节点退出下一个 epoch 的集合,26 个中必须有 18 个保持在线。在线但不投票的验证者同样计入法定人数的不足:自 chain 14 起,验证者会对时间超前自身时钟 15 秒以上的区块不投票,因此时钟偏慢的验证者会在没有任何错误提示的情况下停止投票。请在其上运行 NTP(运行验证者)。
创世直接为注册表播种——每个条目对应一个 --validator <key or hex pubkey>,<stake>,<payout>——并拒绝低于最低值的质押。Chain 21 的创世播种 26 行,每行 1,000 RAND,密钥与 chain 17 至 20 相同。
自 v0.5.11 起,该构建还带有创世归属(vesting):一个公开的限时锁定分配登记表,其中不可撤销的条目可将锁定的 RAND 质押为验证者的质押(rand-node vesting bond),并可取回(rand-node vesting unbond)。它需要创世中有 vesting 部分。没有任何一条链具备该部分,chain 21 也不例外,没有它,这些操作在准入时即被拒绝。(v0.6.8 还允许可撤销的归属条目仅由一个签名者门限向固定的国库撤销,同样只在 vesting 部分下有效。)
公开的内容
| 公开 | 隐藏 | |
|---|---|---|
| 注册表 | 每一行:地址、质押、解除质押队列、未支付的奖励、payout 地址、nonce | 无 |
Bond | 验证者、金额、是否附带注册、bundle 的 burn_r | 哪些 note 支付、谁拥有它们、找零 |
AdmitValidator(自 chain 20 起) | 候选者以及每个投票者及其签名 | 不存在其他内容 |
Unbond | 验证者、金额、nonce | 不存在其他内容 |
Withdraw | 验证者、金额、nonce、note 的 time 和 r | 谁能打开该 note,以及它之后的每一次花费 |
奖励是验证者所提议区块的费用——到 chain 20 为止是全部费用;在 chain 21(fullnode v0.8.0,自 2026-10-12 起上线)上仅为每个 bundle 下限之上的小费,因为创世的 fees 部分会销毁下限本身(费用)。没有区块奖励,也没有通胀;在 chain 21 上,与 chain 20 一样,没有对验证者的罚没(slashing)和监禁(jailing):行为不端的验证者不会损失任何质押,补救措施由运营者自行承担。Fullnode v0.6.8 确实带有针对领导者双签的罚没规则(审计 v6,STAKE-1:同一密钥在同一视图中签署两个区块头,将销毁质押的 equivocation_bps 并监禁该密钥),但仅在创世含有 staking.slashing 部分时才生效,而 chain 20 没有这一部分(rand_getLimits.slashing 为 null);fullnode 建议仅在质押由多于一个运营者持有之后再启用它。(区块聚合代码还带有 SlashAggregator 动作,针对在同一 nonce 上签署两个不同聚合的聚合者,它在 chain 20 上未启用,因为后者没有聚合部分。)
读取
rand validators # the whole register
rand status # is_validator / active_validator for the node asked
→ {"method":"rand_getValidators","params":[]}
← [{ "address": "2nRdFC…", "stake": "1000000000000",
"pending": [{ "release_epoch": 71, "amount": "1000000000000" }],
"rewards": "4000000", "payout": "rand1…", "nonce": 3, "active": true, "jailed_until": null }]
→ {"method":"rand_getAdmitted","params":[]}
← { "admission_by_vote": true, "admitted": [], "max": 256 }
→ {"method":"rand_getEpoch","params":[]}
← { "epoch": 69, "epoch_blocks": 1000, "next_set": ["2nRdFC…", "ByDkxs…"] }
金额是以单位计的十进制字符串。active 表示该行是否处于运行当前 epoch 的集合中;jailed_until 除在罚没部分之下外均为 null;admitted 列出已被投票通过但尚未注册的密钥;next_set 是一个投影,边界之前的每一次质押和解除质押都会使其变动。浏览器的 GET /validators 还增加了 share_percent、blocks_proposed 以及最后提议的高度。