使用本页将程序部署上链并调用:程序是什么、如何构建、一次调用公开什么又隐藏什么、如何读回回执,以及你会遇到的限制。每个程序都是公开代码;每次调用都是一份证明。示例程序提供完整的应用,randprotocol/example-programs 提供部署在 chain 20 上的程序源码(chain 21 于 2026-10-12 割接,启动时没有任何程序:程序不会跨割接保留,示例程序也尚未在其上重新部署),其中包括 RPL-2 程序(见下文)。
什么是程序
程序是面向 Rand zkVM 的 RV32IM 机器码:{ base_pc: u32, words: Vec<u32> },字数最多不超过该链的程序上限(chain 21 上为 65,535 个字,自 chain 13 起每条链皆然;在没有 max_program_words 的创世配置上为 4,096,如 chain 12),base_pc 为 4 的倍数,每个字都能解码为一条指令(无法解码的字在部署时即被拒绝,绝不会拖到运行时)。与 chain 19 和 chain 20 一样,chain 21 还会拒绝部署任何无法被一次调用容纳的程序:没有公共段时超过 8,180 个字,有公共段时则更少(见下文)。程序按内容寻址且不可变:
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
一次调用是一份 STARK 证明,证明摘要为 hc 的程序在某些私有输入上运行过,消耗的 gas 不超过声明的 gas 上限,并发布了八个输出字。节点验证该证明、记录一份回执,仅此而已:输出是数据,不是副作用。调用不能转移 RAND;由调用者在为该调用付费的交易包中转移价值,程序的输出具有何种含义,由程序与其调用者约定。自 chain 20 起,Invoke 是例外:其证明担保一次声明的状态转换,账本随后应用该转换(程序状态(RPL-2))。
guest 程序可以做什么:
| 类别 | 指令 |
|---|---|
| 高位立即数 / 跳转 | LUI AUIPC JAL JALR |
| 分支 | BEQ BNE BLT BGE BLTU BGEU |
| 加载 / 存储 | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| 系统 | ECALL;不含 FENCE、CSR、EBREAK,也不含 A 扩展和 C 扩展 |
系统调用,按 a7 中的编号:
| # | 系统调用 | 作用 |
|---|---|---|
| 0 | HALT | 结束运行 |
| 1 | WRITE_OUTPUT slot word | 固定公开输出 slot < 8;每个槽位至多写一次,未写入的槽位为零 |
| 2 | READ_INPUT idx | 返回私有输入字 idx,与加盐承诺 H_IN 绑定 |
| 3 | POSEIDON2 ptr n | 用 Poseidon2 海绵就地哈希 n ≤ 4096 个字 |
| 4 | KECCAK ptr | 就地执行一次 Keccak-f[1600] 置换;使证明携带 keccak 表 |
| 5 | SHA256 ptr | 就地执行一次 SHA-256 压缩;使证明携带 sha256 表 |
| 6 | READ_PUBLIC idx | 返回公共段的字 idx;自 chain 20 起公共段在部署时固定(rand program deploy --public);在没有 max_program_public_words 的创世配置上它为空,此调用什么也读不到 |
| 7 | POSEIDON2_LEN ptr n | 在海绵中绑定消息长度的 POSEIDON2,因此 [a] 与 [a, 0] 的哈希不同(约束集 7,chain 16 及之后)。circuits aeacf31 处的 rand-guest check 仍会拒绝它(见下一节) |
内存:32 个寄存器,x0 恒为零;RAM 初始为零;所有地址均低于 2³⁰;编译型 guest 拥有 64 KiB 的栈。用 rand-guest 构建的程序在镜像容器中携带其只读数据和已初始化数据,并带有一段属于程序本身的加载前导代码,在程序自身代码运行之前写入这些数据(RISC-V ISA)。票据承诺、废止符和 Merkle 检查不是系统调用,而是建立在 POSEIDON2 之上的库例程。
自 chain 18(约束集 8)起,每次运行都按 gas 计量。普通指令以及系统调用 0、1、2 和 6 各花费 1 gas,对 n 个字的 POSEIDON2 花费 3 + 3 × ⌈n / 4⌉,一次 KECCAK 置换花费 192,一次 SHA256 压缩花费 64。示例程序给出了该表,以及一次调用需要声明的内容。
构建程序
三种方式,均产出 RV32IM 字:
**内置汇编器。**钱包自带五个手工汇编的 guest 程序,并将其汇编为 { "base_pc", "words" } JSON 文件:
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
**用 rand-guest 编译的程序。**circuits 仓库中的 rand-guest 可将 no_std Rust crate(riscv32im-unknown-none-elf,Rust 1.98.1)或 C 目录(clang 23.1.1)构建为镜像容器,按机器的解码器检查每个字,并打印 hc 与程序 id。构建是封闭的,因此同一份源码在任何机器上都得到同一个 hc。对于 chain 18 至 chain 20,请在 circuits 提交 aeacf31(分支 feat/v067-circuits)处构建工具链,fullnode v0.6.7 和 v0.6.8 都固定在该提交。在该提交处,rand-guest check 只列出系统调用 0 至 6,因此调用 guest_sdk::poseidon2_len(系统调用 7)的 guest 会在构建时被拒绝,尽管机器能够运行它(zkp-circuits issue #1)。分步说明:部署 Rust 程序与部署 RISC-V 程序。
转译程序。sbpf2rv 转译 Solana sBPF ELF,evm2rv 转译 EVM 运行时字节码,所得程序的输出与 Rand 解释器 guest 的输出逐字相同:Solana 转译器与 Ethereum 转译器。
钱包本身不含编译器。rand program deploy 接受 .json、由小端字组成的原始 .bin,或 rand-guest 写出的镜像容器(fullnode v0.3 及之后)。
部署
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
testnet 是 chain 21(自 2026-10-12 起上线),在其上部署、调用或 invoke 需要 fullnode v0.8.0 的 rand 钱包。旧版钱包根本无法在那里发送交易:chain 21 运行约束集 9,它更改了每一个验证器密钥和证明编码,因此 v0.6.8 或 v0.7.x 钱包的证明甚至无法解码;并且,与 chain 20 一样,创世配置设置了 binding_domain: 1,因此每个证明绑定和每条签名消息都包含创世哈希。chain 21 启动时没有任何程序:在 chain 20 上部署的内容不会跨割接保留,示例程序也尚未在其上重新部署。公共端点 https://rpc.randprotocol.org 为钱包的同步、部署和调用提供服务(设置)。
钱包首先打印程序 id、字数和 hc,并通过 rand_estimateFee 向节点询问费用;若程序超过该链的上限,该方法会在任何证明之前就拒绝它。随后,钱包构建一个零价值的自转账交易包(每笔交易都携带的那个 4 入 4 出的隐藏资产交易包;费用从其 RAND 槽位中扣除),用其自身的票据支付 0.001 RAND + 100,000 units × words(公共段的字按代码字计),附上 Action::Deploy { base_pc, words, public },针对该交易的绑定对交易包生成证明并提交。自 chain 17 起这是两份证明,钱包为每份各打印一行:一份 tier-10 花费授权(数秒,约 1.37 MB)和 tier-14 交易包(笔记本上约两分钟,约 1.5 MB)。在 chain 17 之前只有交易包证明,在 chain 15 上约 100 秒、约 1.42 MB。部署费用没有 gas 项或字节项:gas 只为调用定价。节点检查每个字都能解码,计算 hc,存储记录,并在后台开始构建验证器密钥。代码是公开的;谁为它付了费则不公开:rand_getProgram 没有 deployer 字段。对同一组字重复部署是幂等的。
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
调用
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
钱包从节点获取代码和公共段,并检查它们的哈希与 id 一致。它在模拟器中运行该调用,并在生成任何证明之前打印 gas 和费用:
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
随后它封存输入记录(见下文),在该链的 FRI 配置下以输入作为私有向量在本地对调用生成证明,再对支付费用的交易包生成证明,提交、等待并打印回执。自 chain 20 起,调用证明绑定到其自身的交易(该绑定包含创世哈希),交易包为两份证明,因此一次调用共有三份:调用证明、tier-10 花费授权和 tier-14 交易包。上面两行来自 2026-09-30 在 chain 18 上对匿名投票程序的调用(匿名投票)。
费用。 自链 20 起,费用下限为 BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉。其中 BUNDLE_BASE 为 0.001 RAND,字节数为调用证明与输入信封之和;两项单价起始为 100 和 800 个单位(一个单位为 10⁻⁹ RAND),并随区块充盈度浮动。链 20 和链 21 将它们的上限设为 10,000 和 80,000 个单位,且只有需要支付字节单价的字节(调用的证明与信封)才计入其负载(byte_load: "paying");在链 18 和链 19 上,单价没有上限,每笔交易的字节都会推动字节单价。账本中固定的 0.001 RAND + call_fee(tier) 费率表,即链 15 上的规则,在这些链上不再适用。钱包在证明生成之前就要确定费用,因此按该链的证明上限(链 20 为 4 MiB,链 21 为 6 MiB)计价,并额外留出两个价格步长的余量。在链 21 上,整个费用下限都被销毁(创世配置 fees.burn_floor):提议者只保留发送方在下限之上多付的部分,rand_getSupply.base_fees_burned 统计被销毁的数额(费用)。在链 18 上实测的四次调用,每次花费 0.0057 至 0.0075 RAND(计算过程)。rand fee call <tier> --gas <limit> --bytes <B> 会向节点查询费用下限本身。private_payment 读取四个私有余额,若其总和不小于阈值,则公开盈余部分:输出为 [1, 0, 25, 0, 0, 0, 0, 0]。链上能看到证明、tier 以及这八个字;看不到各项余额。
标志:--tier T 强制指定 tier;--gas-limit <N|max> 声明确切的 gas 限额或该 tier 的上限,以取代默认值,默认值为将确切 gas 向上取整到 2^(tier − 2) 的倍数;--fee <RAND> 设定费用;--expect-public <FILE> 在证明之前拒绝执行,除非程序的公开段正是该文件中的字;--auditor <rand1…> 同时为指定的审计方封存转录;--print-call-key 打印单次调用密钥;--no-envelope 不发布转录;--cuda 选择 GPU 后端(尚未在硬件上测试,且与封存信封不兼容)。链 20 在调用上直接拒绝 --cuda:与其交易绑定的调用证明在 CPU 后端上生成。
回执
每次已提交的调用都会存储一份回执,并在节点同步或验证其链时重新计算:
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in 是证明对私有输入的加盐承诺;单独来看,它不披露任何信息。h_pub 是程序在部署时的公开段的摘要,即程序的 public_digest;对于部署时没有公开段的程序,则为 null。回执中没有 effect 字段,声明的 gas 限额也不在回执中。对于 call,浏览器在 GET /transactions/:hash 上以 receipt 展示同一对象,在 GET /programs/:id 上列出某个程序的调用,并支持 GET /transactions?kind=call&program=<id> 过滤。
输入信封:事后披露一次调用
默认情况下,rand call 会把调用的 (salt, inputs) 转录封存在一个 call-input 信封中发布,这样日后无需第二次证明即可展示输入:
| 部分 | 字节 | 含义 |
|---|---|---|
body | 12 + 16 + 4·n + 16 | 在全新的单次调用密钥 K_call 下的 salt ‖ inputs |
to_sender | 60 | 包裹给调用方出站查看密钥的 K_call |
kem_ct | 1,088 或 0 | 向审计方的 ML-KEM-768 封装;无审计方时为空 |
to_auditor | 60 或 0 | 在该封装的秘密之下包裹的 K_call |
body 的关联数据为 H_IN,因此被转移到另一次调用上的信封对任何人都无法打开。链只检查大小(至多为 max_call_envelope_bytes:链 20 和链 21 为 65,536 字节,没有该字段的创世配置为 18,432 字节),并通过 rand_getCallEnvelope(tx) 原样提供。有三种密钥可以打开它,仅此而已:调用方的查看密钥(该钱包发出的每一次调用)、单次调用密钥(仅那一次调用)、审计方的查看密钥(仅那一次调用)。
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call 会打开转录,检查 input_digest(salt, inputs) == H_IN,通过模拟器在恢复出的输入上重新运行程序,并将输出与回执中的输出比较;若任一检查失败,则以非零状态退出,因此脚本不会把警告误当作肯定。--no-envelope 不可逆:盐一旦丢失,任何人(包括调用方)都无法再打开那次调用。
自 v0.6.8 起,重新运行是在证明所基于的公开段上进行的:即程序在部署时的公开字,以及在 hardening_v6 之下紧随其后的八字调用绑定(fullnode issue #116)。在 v0.6.7 中,open-call 只适用于部署时没有公开段的程序;对于有公开段的程序,它会先打印输入,然后以 re-running the program on these inputs failed: PublicIndex(0) 终止,以非零状态退出,两项检查都没有执行。
程序状态(RPL-2)
链 20 是第一条带有 RPL-2 的链,由其创世配置的 program_state 部分开启;链 21 沿用该部分,取值相同。程序因此获得三样此前没有的东西(fullnode docs/program-state.md,v0.6.8 的 crates/randprotocol-core/src/ledger/program_state.rs):
| cells | 每个程序一张从 8 字键到 8 字值的映射。不存在的 cell 读出为八个零;写入八个零即移除它。提交在状态根中 |
| vault | 每种资产一份公开余额(0 为 RAND)。价值通过一次 invoke 的 bundle 进入,并以链计算出的票据形式流出 |
| 专属代币 | 用 rand token create --program <id> 注册的代币(铸造权限为 Program(id),无初始供应量)只能由该程序的 invoke 铸造和销毁 |
cell 和 vault 余额是公开的,与代币的供应量一样。其中没有任何东西是票据。
Invoke(Action 33)是一种同时声明状态转换的调用,其调用证明表明程序恰好接受该转换:
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
流入的是 bundle 所销毁的部分:burn_r 是存入 vault 的 RAND;burn_asset 的 burn_a 是被存入(流入类型 deposit)或被销毁(流入类型 burn,仅限程序自己的代币)的代币。流出的是每笔支付对应的一张由链计算的票据,先支付后铸造,金额与接收方的 pk 为公开,与铸造的情形相同。只有当程序存在、形状合法、reads 中的每个 cell 仍持有所声明的值、vault(计入本交易自身的存入之后)足以覆盖每一笔支付、每一次铸造都是程序自己的代币,并且证明验证通过时,账本才会应用该转换。不再匹配的读取会以 StaleRead 被拒绝;钱包会重新读取并重新证明。
invoke 与 call 的区别。
Call(RPL-1) | Invoke(RPL-2) | |
|---|---|---|
| 证明所基于的公开段 | public ‖ call_binding | public ‖ call_binding ‖ context,即以字表示的状态转换 |
| 成功时链的动作 | 存储回执;输出是数据 | 存储回执并应用状态转换:写入 cell、移动 vault、追加支付票据 |
| 价值 | 仅限 bundle 自身的转账 | 通过 bundle 的销毁存入 vault;支付与铸造从中流出 |
| 是否依赖状态 | 否 | 证明不依赖(公开段由交易构造),但读取检查依赖,在准入时和交易池中进行 |
| 费用 | call 费用下限 | call 费用下限,外加每创建一个 cell 的 cell_fee |
程序用 READ_PUBLIC(public_len + 8 + i) 读取上下文字 i。字 0 为版本(1),字 1 至 4 为读取、写入、支付和铸造的数量,随后是 burn_r、流入类型、burn_asset 和 burn_a,再后是每个读取和写入的键与值(各 8 个字),最后是每笔支付的资产与金额。接收方不在上下文中:程序决定金额,由绑定确定谁被支付。程序必须约束其所见到的每一个上下文字,并且不得有任何会 panic 的路径,因为 guest-sdk 的 panic 处理程序会停机,而停机的运行是可证明的运行。
链 21 的限制(rand_getLimits.program_state,与链 20 相同):
| 限制 | 链 21 |
|---|---|
cell_fee | 每创建一个 cell(在原本没有存储的位置写入非零值)10,000,000 个单位,即 0.01 RAND;重写或删除 cell 免费 |
max_reads | 8 个 cell |
max_writes | 8 个 cell |
max_payouts | 4 张票据,支付与铸造合计 |
公开段规则通常更紧:整个公开段必须能放入对同一程序的调用所基于的公开表,对于没有公开输入的程序为 127 个字。这样剩下 119 个上下文字,例如三次读取、三次写入和四笔支付。
命令与 RPC(钱包 v0.6.8 及更高版本):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke 在证明之前会对照链上实时 cell 检查每一个 reads 条目,遇到过期读取时以退出码 3 退出(可能发生在证明之前,也可能发生在上链确认时),因此脚本需要重新读取并重试。带有 "kind": "invoke" 的 rand_estimateFee 用于报出费用。transition 文件的格式见 fullnode 的 docs/cli.md。部署在 chain 20 上的完整 RPL-2 程序(amm、stableswap、orderbook、stablecoin、lending、perp、crowdfund、stoploss),以及 RPL-1 程序(sealed-auction、ballot、eligibility、private-join、credit-score),均收录于 randprotocol/example-programs。
Chain 21 还承载了 RPL-3,永续期货,它是创世时的 perps 部分,而非已部署的程序:只有一个市场 BTC-PERP,以 zUSD 结算,其撮合引擎是一个固定的 guest(engine_hc 为 f286a611…1573,由交易场所自己的 prover 在链下以 tier 18 证明),其状态拥有独立的创世根。它有六个自己的操作(标签 34 至 39)和四个读取接口:rand_getPerps、rand_getPerpAccount、rand_getPerpAccounts 和 rand_getPerpInputs(参见 Transactions、JSON-RPC)。你部署的程序无法调用它。
哪些是公开的,哪些是隐藏的
| 公开 | 隐藏 |
|---|---|
程序 id 及其代码;tier;声明的 gas 上限;八个输出;H_IN;bundle 的费用和公开字段;信封的大小;调用使用的是 Keccak 还是 SHA-256,以及大致的使用频率(证明头部声明了每个哈希表的高度) | 私有输入;寄存器、内存、分支;真实的周期数和确切的 gas;H_IN 的盐;调用者是谁,以及用什么支付 |
同一次运行的两个证明,字节并不相同。在 chain 15 及更早的链上,这一点并不足够:私有字数不超过 31 的调用,其证明的表过小而无法隐藏,输入可以从证明中恢复出来。自 v0.6 起,prover 已将这些表的下限设为 128 行;约束集 7(chain 16 及之后)则对仍会泄露的各表总量进行了盲化。hc 具有绑定性,但不具有隐藏性:任何能够枚举候选程序的人,都可以确认部署的是哪一个。
目前尚不可能的事项
以 fullnode 自身的表述为准:
- 没有跨程序调用,RPL-2 之外也没有状态。 一次
Call是对一次运行的一个证明;除调用者回传的内容外,调用与调用之间不会携带任何东西。自 chain 20 起,Invoke可以读写程序自己的 cell 和金库(见上文);chain 20 之前的链都没有程序状态,且 invoke 无法触碰其他程序的 cell。 - 金库之外没有由程序驱动的转账。 效果类型 1,即由程序向账户支付的转账,已随账户一并删除,
Call的输出只是声明。自 chain 20 起,Invoke从程序自己的金库中支付,至多四个 note,金额和收款方均为公开。 - 钱包中没有编译器。 程序要么是内置的已汇编 guest,要么是在电路仓库中用
rand-guest构建的镜像。 - 公开输入在部署时固定,而非每次调用时指定。 约束集 6 增加了
READ_PUBLIC和H_PUB。自 chain 20 起,一次部署最多可携带 32,768 个公开字,程序 id 绑定这些字,每次调用的证明都会对照它们的摘要进行检验。不存在每次调用单独的公开字。 - 哈希系统调用会产生很大的证明。 携带 keccak 的证明约为 3.2 MB,直接超过默认 2 MiB 的证明上限;携带 sha256 的证明会再增加约 400 KB。Chain 21 的上限是 6,291,456 字节(6 MiB,在其切换时为 private-multisig 授权证明而提高;chain 17 至 20 为 4 MiB,chain 13 至 16 为 8 MiB),两者都可容纳:在 chain 18 上,带 Keccak 表的 tier-12 调用实测为 3.35 MB,带 SHA-256 表的 tier-14 调用为 1.91 MB(Example programs)。一次调用还限制为 128 次 Keccak-f 置换和 128 次 SHA-256 压缩(见下文),且证明的每一 KiB 都按字节价格计费。
- EVM 和 Solana 程序无法在 chain 21 上部署,其调用也超出了调用 tier 上限。 解释器 guest(
evm.bin,18,009 字;sbpf.bin,8,317 字)和翻译后的镜像(ERC-20,11,686 字;SPL Token,65,096 字)超出默认的 4,096 字上限,但低于 chain 13 设定并被此后每条链保留的 65,535 字上限。两个翻译后的镜像均于 2026-09-19 在 chain 13 上部署,SPL Token 以其 ELF 作为公开输入。Chain 14 和 15 是全新切换的,两者上均未重新部署任何一个镜像。与 chain 19 和 20 一样,chain 21 拒绝部署这四者中的任何一个:根据其hardening_v6规则,凡是没有任何 tier-14 调用能够容纳的程序(无公开段时超过 8,180 字,有公开段时更少)在部署时即被拒绝(ProgramUncallable),这样就不会为无人能调用的程序支付部署费。ERC-20 调用在 tier 16 或 18 证明:tier-16 的approve于 2026-09-19 在 chain 13 上以 3,412,405 字节的生产证明被调用(Ethereum transpiler),而 chain 21 拒绝任何高于 tier 14 的调用(见下一条),因此目前那里不接纳任何 ERC-20 调用。SPL Token 调用为 tier 20,需要约 330 GB;目前不存在 SPL 调用证明(证明内存)。EVM 区块上下文操作码会触发陷阱,链上也没有 EVM 状态。 - 调用在 tier 14 或以下证明。 自 fullnode v0.5.6(2026-09-25)起,高于 tier 14 的调用证明会被拒绝(
CallTierTooHigh),超过 128 次 Keccak-f 置换或 128 次 SHA-256 压缩的证明同样被拒绝。Chain 13 上 tier-16 的 ERC-20approve在 chain 21 上会被拒绝,正如它在 chain 15 和 chain 18 至 20 上本会被拒绝一样,每一个 tier-18 的 ERC-20 调用或 tier-20 的 SPL Token 调用也是如此;这些仍可在链下证明(预算)。 - GPU 证明从未在硬件上运行过。
--cuda需要--features cuda构建和已提交的 PTX;在 chain 20 切换时(2026-10-01),集群上两者都不存在。在集群上,调用证明由 CPU 后端生成;自 v0.7.1 起,--features cuda构建在检测到 GPU 时默认选用 GPU,--cpu则强制使用 CPU。 hc不具有隐藏性,如上所述,而且见证请求仍会告诉节点钱包花费的是哪一片叶子。