使用本页构造、提交或解码一笔交易:rand_sendTransaction 接受的线上格式、交易 id 的计算方式、全部 action 及其 bundle 与费用规则,以及决定你最先遇到哪个错误的准入顺序。
交易
Transaction { chain_id: u64, bundle: Option<Bundle>, action: Action }
Bundle { // the hidden-asset bundle (since chain 14)
anchor: Word8, nullifiers: [Word8; 4], commitments: [Word8; 4],
fee: u64, burn_a: u64, burn_r: u64, burn_asset: u32, time: u32,
envelopes: [Envelope; 4], proof: Vec<u8>, // postcard(Proof) of the hidden-asset guest
auth_commit: Word8, auth_proof: Vec<u8>, // since chain 17: split authorisation's c and its auth proof
}
Envelope { kem_ct: Vec<u8>, to_receiver: Vec<u8>, to_sender: Vec<u8>, body: Vec<u8> }
Word8 是八个小端序 u32 字,渲染为 64 个小写十六进制字符。线上格式为 bincode(Transaction),使用 bincode 的默认设置(定长整数、8 字节长度前缀、u32 枚举标签),rand_sendTransaction 以十六进制接收它。交易上没有签名,也没有发送方字段:bundle 由其证明自行授权(bundle 证明,以及自 chain 17 起与之并列的 auth 证明),而带签名的 action 则把签名放在自身内部。重放保护如下:bundle 靠 nullifier 集合,带签名的质押 action 靠登记表中每个 validator 的 nonce,TokenMint 与 SetAuthority 靠每个代币的 mint_nonce,桥的治理 action 则靠桥的 pause_nonce 与 list_nonce。
一笔交易至多携带一个 bundle,且每个非无 bundle 的 action 恰好携带一个。bundle 的 0–1 号槽位承载私有资产,2–3 号槽位承载 RAND,因此一个证明即可转移一种代币并支付其 RAND 费用;早期设计中桥销毁所用的两个 bundle 已不复存在:一次销毁就是一个 bundle(Notes)。
rand 钱包通过 randprotocol_client::wallet::{send, submit} 构建这一切:选择输入、获取 anchor、用钱包自己持有的树副本计算见证、构建输出并封装全部四个信封、取得交易绑定、证明 bundle 与 auth 证明、检查 bundle 证明公布的摘要、提交。
交易绑定
交易没有签名,而 bundle 的证明只承诺 bundle 自身的摘要:anchor、nullifier、承诺、费用、销毁字段、time。直到 chain 13,没有任何机制把已证明的 bundle 与其所附的 action 或其信封绑定在一起,因此任何看到交易的人都可以保留全部证明字节而更改其余部分:BridgeBurn 的目的地、Bond 的 validator、某个信封。自 chain 14 起,每个 bundle 证明都针对交易绑定生成,并依据交易绑定验证:
binding(tx) = blake3("rand-tx-bind-1" || bincode(chain_id, bundle', action')) chains 14 to 19
binding(tx) = blake3("rand-tx-bind-2" || bincode(genesis_hash, chain_id, bundle', action')) chain 20
as 8 LE u32 words
其中 ' 表示每个 bundle 证明字节串都被替换为空向量:bundle.proof、自 chain 17 起的 bundle.auth_proof,以及 Aggregate 的 proof。其余一切都在其中:auth_commit、全部四个信封、每个销毁字段、目的地、validator、接收方、签名、守护者证明及其 Dilithium2 联合签名,以及 Call 自身的证明。这八个字随证明的公开输入段一同传递,该段的摘要 H_PUB 已由电路绑定,因此该修复无需改动 guest 或电路;节点运行 Machine::verify_public(hc_bundle, binding, proof),并拒绝声明了任何其他公开高度的 bundle 证明(public height not the transaction binding's)。Transaction::binding 是钱包在证明之前、账本在验证之前调用的同一个函数。自 chain 17 起,auth 证明针对同样的八个字生成并验证,因此它也无法被挪到另一笔交易上。
Chain 20 绑定创世哈希。 直到 chain 19,交易绑定以及无 bundle action 的签名消息只涵盖链 id,因此两条共用同一链 id 的链(重新切分的链、私有副本)会互相接受对方的证明与签名。Chain 20 的创世设置了 binding_domain: 1(v0.6.8,审计 v6 BIND-1;crates/randprotocol-core/src/types/binding.rs)。此后创世哈希位于每个原像的开头,并使用高一个版本的标签:两种绑定分别为 rand-tx-bind-2 与 rand-call-bind-2,水龙头铸造(rand-mint-3)、Unbond 与 Withdraw(rand-unbond-2、rand-withdraw-2)、RPL 铸造与权限消息(rand-rpl-mint-2、rand-rpl-authority-2)、aggregator action 以及桥的治理消息同理。此类链上的 validator 登记始终是 v2 消息(创世、链、地址、收款)。为某一条链证明或签名的任何内容,无论其链 id 为何,都无法在另一条链上通过验证。绑定字是公开输入,因此 guest 与验证器密钥均未改变。早于 v0.6.8 的钱包仍针对链 id 形式生成证明,在 chain 20 上会被拒绝。rand_getLimits 与 rand_status 提供 binding_domain(chain 20 上为 1,没有该字段时为 0)。
在创世 hardening_v6 下(chain 16 至 20),Call 自身的证明同样被绑定,方式是由同样构造、使用其自有域的第二个摘要完成,其中 call 证明与 bundle 的证明一样被置空:
call_binding(tx) = blake3("rand-call-bind-1" || bincode(chain_id, bundle', action'')) as 8 LE u32 words
在 chain 20 上,标签为 rand-call-bind-2,创世哈希位于原像开头,如上所述。Invoke 的证明以同样方式绑定,其声明的转换保留在原像中,因此该证明恰好承诺交易所携带的状态变更、支付与接收方。
call 证明在其公开输入段中携带这些字,位于程序的部署时公开输入(若有)之后,因此将其复制并附加到另一个费用 bundle 上将无法通过验证。两种绑定相互嵌套:钱包选定费用 bundle 的 note,在证明为空的情况下构建交易,针对 call 绑定证明该 call,将该证明放入,然后才取得交易绑定并证明 bundle。
对客户端的影响是:在证明之前构建完整的交易,包括输出、信封与 action。在分叉之前构建的钱包针对空的公开段生成证明,会被拒绝。无 bundle 的 action 没有可绑定的证明,依靠其自身的签名。该绑定不是交易 id,交易 id 保持不变。
交易 id
txid = blake3("rand-txid-3" || bincode(view))
其中 view 是将 bundle 的 proof 字节替换为 blake3(proof)(proof_hash)、将其 auth_proof 字节替换为 blake3(auth_proof)(auth_proof_hash,位于 auth_commit 之后,auth_commit 按原样进入)的交易,其余每个字段保持原样。直到 chain 16,其域为 rand-txid-2,不含这两个 auth 字段;每个交易 id 都随 chain 17 而改变。按摘要对证明做哈希,使得被裁剪的 bundle(区块聚合,在 chain 20 上未启用)与其原始形式保持相同的 id;被裁剪的 bundle 保留其 auth 证明。无 bundle 的交易按其字节原样哈希。同一个 id 是内存池、收据和所有 RPC 查询的键。
全部 action
Action 是一个 bincode 枚举,标签按声明顺序排列:
| 标签 | 操作 | 字段 | 捆绑包 | 费用下限 |
|---|---|---|---|---|
| 0 | None | — | 必需 | BUNDLE_BASE = 0.001 RAND |
| 1 | Mint | cm, pk, time, r, envelope, amount, minter: PublicKey, signature | 必须不存在 | 0(由验证者签名;仅限水龙头链,amount ≤ 100 RAND) |
| 2 | Deploy | base_pc: u32, words: Vec<u32>, public: Vec<u32> | 必需 | 基础费用 + 每个程序字和公开字 100,000 单位 |
| 3 | Call | program, proof, input_envelope: Option<CallEnvelope> | 必需 | 在 chain 20 上,基础费用 + gas_price × 证明所声明的 GAS_LIMIT + 证明与输入 envelope 每 KiB 的 byte_price;在没有 gas 部分的链上,基础费用 + call_fee(tier) |
| 4 | Bond | validator, amount, registration: Option<Registration> | 必需,且 burn_r == amount | 基础费用 |
| 5 | Unbond | validator, amount, nonce, signature | 必须不存在 | 0 |
| 6 | Withdraw | validator, amount, nonce, time, r, envelope, signature | 必须不存在 | 链上为 0;基础费用从 amount 中扣除 |
| 7 | BridgeAttest | attestation, recipient: ShieldedAddress, r, time, asset, envelope, pq_signatures: Vec<PqSignature> | 必需 | 基础费用 |
| 8 | BridgeBurn | asset, amount, relayer_fee, to_chain: u16, token: [u8; 32], to: [u8; 32] | 必需,且 burn_asset == asset、burn_a == amount | BRIDGE_BURN_FEE = 0.01 RAND |
| 9 | RegisterAggregator | registration | 必需,burn_r = 保证金 | 基础费用 |
| 10 | UnbondAggregator | aggregator, nonce, signature | 必须不存在 | 0 |
| 11 | WithdrawAggregator | aggregator, nonce, time, r, envelope, signature | 必须不存在 | 0 |
| 12 | SlashAggregator | 两个已签名的聚合区块头 | 必须不存在 | 0 |
| 13 | Aggregate | covers, proof, aggregator, nonce, time, r, envelope, signature | 必须不存在 | 0 |
| 14 | RegisterToken | name, symbol, decimals: u8, authority: MintAuthority, initial: Option<InitialMint>, salt: [u8; 32], index: u32 | 必需 | 基础费用 + 注册表的 registration_fee(chain 20 上为 1 RAND,予以销毁) |
| 15 | TokenMint | asset, amount, recipient: ShieldedAddress, r, time, envelope, nonce, signature | 必需 | 基础费用 |
| 16 | SetAuthority | asset, new: Option<PublicKey>, nonce, signature | 必需 | 基础费用 |
| 17 | TokenBurn | asset, amount | 必需,且 burn_asset == asset、burn_a == amount | 基础费用 |
| 18 | PauseMints | nonce, signature | 必须不存在 | 0 |
| 19 | UnpauseMints | nonce, pq_signatures | 必须不存在 | 0 |
| 20 | RegisterBridgedToken | name, symbol, salt, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatures | 必需 | 基础费用 + registration_fee |
| 21 | ListBacking | token_index, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatures | 必需 | 基础费用 |
| 22 | RotatePqGuardians | new_pq_guardians: Vec<PublicKey>, nonce, pq_signatures | 必须不存在 | 0 |
| 23 | RotatePauseKey | new_pause_key: PublicKey, nonce, pq_signatures | 必须不存在 | 0 |
| 24 | ClaimVested | entry: [u8; 32], amount, nonce, to: ShieldedAddress, time, r, envelope, signature | 必须不存在 | 链上为 0;基础费用从 amount 中扣除 |
| 25 | RevokeVesting | entry: [u8; 32], unvested, nonce, to: ShieldedAddress, time, r, envelope, signatures(自 v0.6.8 起为该条目撤销者的一个阈值签名,向其创世条目所指定的国库付款;此前为单个 signature) | 必须不存在 | 链上为 0;基础费用从 unvested 中扣除 |
| 26 | BondVested | entry: [u8; 32], validator, amount, registration: Option<Registration>, nonce, signature | 必须不存在 | 0 |
| 27 | UnbondVested | entry: [u8; 32], amount, nonce, signature | 必须不存在 | 0 |
| 28 | AdmitValidator | candidate: PublicKey, signatures: Vec<(PublicKey, Signature)> | 必须不存在 | 0 |
| 29 | SlashEquivocation | first, second:同一领导者在同一视图中签名的两个区块头 | 必须不存在 | 0 |
| 30 | RotatePqGuardiansV2 | new_pq_guardians: Vec<PublicKey>, possession: Vec<Vec<u8>>, nonce, pq_signatures | 必须不存在 | 0 |
| 31 | RotatePauseKeyV2 | new_pause_key: PublicKey, possession: Vec<u8>, nonce, pq_signatures | 必须不存在 | 0 |
| 32 | CancelRotation | kind: u8, nonce, signature | 必须不存在 | 0 |
| 33 | Invoke | program, proof, input_envelope: Option<CallEnvelope>, transition: Transition | 必需;burn_r 以及 burn_asset 的 burn_a 即为进入程序的部分 | 一次调用的费用下限 + 每创建一个 cell 的 cell_fee |
MintAuthority = None | Key(PublicKey) | Bridge { backings: Vec<Backing> } | Program(Hash)
InitialMint { amount: u64, recipient: ShieldedAddress, r: Word8, time: u32, envelope: Envelope }
PqSignature { index: u8, signature: Vec<u8> } // one guardian's Dilithium2 co-signature, by set index
在 chain 20 上,标签 0 至 8、14 至 21、28 以及 30 至 33 可用。其创世配置含有带 rules_v2 的 bridge 部分、tokens 部分、staking.admission_by_vote(标签 28)、bridge.rotation(标签 30 至 32)和 program_state(标签 33)。标签 22 和 23 在该链上被拒绝:在 bridge.rotation.needs_possession 之下,轮换必须采用 …V2 形式,携带每个新密钥自己的签名。chain 19 没有上述任何部分,标签 0 至 8 和 14 至 23 可用(没有 rules_v2 的 chain 14 拒绝 22 和 23)。SlashEquivocation 需要 staking.slashing 部分,而 chain 20 的该部分为 null。五个聚合操作需要 aggregation 部分,chain 20 没有该部分(在重新测量聚合之前,构建版本会拒绝在带有该部分的创世配置上启动)。节点在其他任何检查之前,就在入口处拒绝所有这些操作,但它们仍占用各自的 bincode 标签,因此硬编码标签编号的编码器必须把它们计算在内。聚合在 证明 中被记述为即将推出。四个创世归属(vesting)操作(标签 24 至 27,自 v0.5.11 起已在构建中)需要 vesting 部分,而没有任何一条链有过该部分,chain 20 也不例外,因此没有该部分时它们在准入时被拒绝。标签 28 至 33 是 v0.6.8 新增的,并追加在最后,因此此前每条链的历史仍可解码。
四个 RPL 操作(标签 14 至 17)分别用于注册代币、增发由 Key 授权的代币、转让或放弃其铸造权限,以及销毁持有的代币;指定 Bridge 或 Program 权限的 RegisterToken 会被拒绝。没有代币转账操作:代币转账就是 None,与 RAND 支付无法区分。参见 RPL 代币。四个桥治理操作(标签 18 至 21)分别是铸造暂停、解除暂停,以及在创世之后上架桥接代币或新增一项抵押支持。PauseMints 由创世暂停密钥签名,该密钥只能暂停;其余三个需要 Dilithium2(PQ)守护者的法定多数签名。BridgeAttest 在其 ECDSA 法定多数签名之外,同样携带这样一组法定多数签名。在桥的 rules_v2 之下,标签 22 和 23 用于替换 PQ 守护者集合或暂停密钥,各自需要当前集合的 PQ 法定多数签名以及桥的 rotation_nonce。标签 30 和 31 在 bridge.rotation 之下做同样的事,并且还携带每个新密钥自己对轮换消息的 Dilithium2 签名,以证明该密钥确由持有者掌握;在 chain 20 上,轮换先被记为待生效,并在 delay_secs(86,400 s)之后生效。CancelRotation(标签 32)由当前暂停密钥签名,在待生效的轮换生效之前将其撤销。参见 桥。
AdmitValidator(标签 28)是验证者集合对准入新密钥的投票:签名来自持有超过三分之二质押的验证者,按地址升序排列。在 staking.admission_by_vote(chain 20)之下,若某个 Bond 会注册一个新的验证者,则除非其地址已通过这种方式获准,否则该 Bond 会被拒绝(质押)。Invoke(标签 33,RPL-2)是一次调用,其证明担保程序的一个已声明状态 transition:读取和写入的 cell、捆绑包的销毁字段带入程序金库的内容,以及金库支付的票据或程序铸造的票据。只要每个已声明的读取仍然成立且两个证明均验证通过,账本就应用该转换(程序)。
Deploy 和 Call 是普通的屏蔽交易,其捆绑包是一笔金额为零的自我转账:它的存在是为了用调用者自己的票据支付费用下限,因此链永远不知道这些票据属于谁。RAND 只能通过 burn_r 离开池——Bond 的质押、RegisterAggregator 的保证金、Invoke 存入程序金库的存款——而代币只能通过指定它的 burn_asset 的 burn_a 离开,见于 TokenBurn、BridgeBurn 或 Invoke。其他所有操作都不销毁任何东西,而 burn_asset = 0 且 burn_a 非零的捆绑包在任何地方都会被拒绝。
准入顺序
Ledger::validate_inner 先检查成本低的项目,再检查成本高的项目,顺序如下;内存池在广播之前运行同样的检查,因此一笔无效交易只会在边缘被拒绝一次。
- 大小上限:整笔交易 ≤ 链的
max_block_bytes;捆绑包的四个信封,以及该操作携带的任何票据信封,都必须恰好等于创世的envelope_bytes(chain 20 上为 1,860;没有该字段的链上至多 2,048 字节);捆绑包证明、授权证明和调用证明各 ≤max_proof_bytes(默认 2 MiB,chain 20 的创世中为 4 MiB;chain 13 至 16 上为 8 MiB);程序 ≤ 链的程序上限(默认 4,096 个字,chain 20 的创世中为 65,535);调用信封 ≤max_call_envelope_bytes(默认 18,432 字节,chain 20 上为 65,536);证明声明(attestation)≤ 16 KiB。封存(已裁剪)标记形式的捆绑包证明,在封存形式同步之外于此处被拒绝(PrunedFormOutsideSync),且发生在任何证明工作之前。 - 链 id 匹配。
- 形态与费用下限:无捆绑包的操作不携带捆绑包,其余所有操作必须恰好携带一个;
check_burn_shape规定操作可以设置三个销毁字段中的哪一个:TokenBurn或BridgeBurn通过burn_asset和burn_a销毁其自身的asset和amount,Bond或RegisterAggregator通过burn_r销毁其数额,其他所有操作都不销毁任何东西;fee ≥ fee_floor(action)。在有gas部分的链(chain 20)上,Call或Invoke在此步骤的下限是声明上限为 1 时的 gas 规则,即对其证明和输入信封计算BUNDLE_BASE + gas_price + byte_price × ⌈bytes / 1024⌉,这些在任何验证之前即已知。 - 锚点:chain 20 上为最近 1,024 个区块末根之一,其创世设定
proof_window_blocks: 1024(v0.6.8);没有该字段的链上为最近 256 个之一,如 chain 19 及之前。 - 时间:chain 20 上在
[height − 1024, height]之内;没有该字段时为[height − 256, height]。同一个创世值同时设定两个窗口。 - 作废符与承诺:四个作废符互不相同且均未被花费;四个承诺互不相同且均不是已有的叶子。
- 操作检查:水龙头已启用,铸造数额低于上限且经验证者签名,并且在 chain 20 上,指定的接收方在创世
staking.faucet_recipients允许列表中,符合该纪元的faucet_budget_per_epoch,并由创世staking.faucet_minters列表中的验证者签名(自 chain 16 起为有效性规则);任何链上,达到或超过 2⁶³ 的铸造或存入都会被拒绝;程序可解码;程序存在;质押规则(注册信息恰好在验证者未知时出现、nonce、签名、质押充足);桥自身对BridgeAttest和BridgeBurn以及暂停和上架操作的规则;RPL 对其四个操作的规则,包括注册费;在 chain 20 上还有准入投票的法定人数,对于Invoke,还有转换的形态、对照已存储单元的声明读取、金库对每笔支出的覆盖以及单元费。所有这些都在捆绑包证明之前运行,因此无法应用的交易不会耗费任何 STARK 验证。在hardening_v6下(chain 16 至 20),此处还有两项拒绝:对任何调用都无法持有的程序的Deploy(ProgramUncallable),以及头部或转录中带有诚实证明者不会写入的字段的捆绑包证明或调用证明(non-canonical proof: …),二者均在任一证明被验证之前被拒绝。自 v0.6.8 起,头部声明的度数超过最高表上限的证明,会在核查遍历它之前,以同一裁决首先被拒绝(审计 v6 ZKV-4)。 - 捆绑包摘要:在拆分授权的链上(自 17 起的每条链),首先检查授权字段:没有授权证明的捆绑包被拒绝,授权证明发布的
c(读取,尚未验证)必须等于auth_commit,并且在 chain 20 上授权证明必须声明恰好 1,279 的GAS_LIMIT。然后账本重新计算捆绑包已发布字段(包括auth_commit)的摘要,它必须等于证明所发布的摘要。在 chain 20 上,捆绑包证明还必须声明恰好等于创世bundle_gas_limit的GAS_LIMIT,即 20,479。 - 捆绑包证明:针对创世固定的
hc_bundle和交易的绑定执行Machine::verify_public。在拆分授权的链上,接着针对创世固定的hc_auth和同一绑定验证授权证明。 - 调用证明及其层级费用:对于
Call或Invoke,首先固定证明的头部(层级至多 14,keccak 表至多 2¹²,sha256 表至多 2¹³,已部署程序的高度,在hardening_v6下下限为 2⁷ 行,并且在 chain 20 上声明的GAS_LIMIT不大于头部的上限gas_max),然后针对程序的code_hash,并在hardening_v6下针对交易的调用绑定,验证调用的 STARK。费用最后检查,因为只有在证明解码之后才能得知:在 chain 20 上,按当前生效的价格,fee ≥ BUNDLE_BASE + gas_price × GAS_LIMIT + byte_price × ⌈bytes / 1024⌉,Invoke还要加上每个创建的单元的cell_fee(费用);在没有gas部分的链上,fee ≥ BUNDLE_BASE + call_fee(tier)。
错误由 rand_sendTransaction 以代码 -32000 连同原因返回:nullifier already spent、anchor is not one of the last 1024 roots(没有 proof_window_blocks 的链上为 256)、time 12 is outside [476, 1500]、fee 1000000 below minimum 2000000、the bundle's digest is not what its proof published、invalid bundle proof: …、unknown program …、already in mempool、conflicts with a pending transaction over <nullifier>、faucet is disabled on this chain,自 chain 14 起还有 the bundle burns asset 2 on an action that burns no token、a burn of 5 is not allowed on this action、the bundle burns 399, not the 400 the action declares、wrong mint nonce: expected 1, got 0、wrong token index: expected 3, got 2,自 chain 17 起还有 this chain requires an auth proof on every bundle (genesis hc_auth) 和 the auth proof publishes a different commitment than the bundle's auth_commit,以及在 chain 20 上的 envelope is 1348 bytes, this chain requires exactly 1860 和 bundle proof declares gas limit …, this chain requires exactly 20479。接受不等于提交:请轮询 rand_getTransaction,直到它返回一个区块。
包含任何无效交易的区块都是无效的。内存池为每个待处理的作废符和承诺建立索引,并直接拒绝冲突,因此花费同一张票据的两个捆绑包永远不会出现在同一个区块中。
大小
| 部分 | 字节 |
|---|---|
Word8 | 32 |
一个 Envelope | 自 chain 18 起编码后为 1,892(密文 1,860);chain 17 及之前为 1,380(1,348) |
去掉两个证明的 Bundle | 7,936(四个作废符、四个承诺、四个信封、auth_commit) |
| 去掉两个证明的转账交易 | 7,949 |
| 去掉两个证明的部署交易,100 个字的程序,无公开输入 | 8,369 |
| 去掉三个证明的调用交易,无输入信封 | 7,990 |
| 铸造交易(无捆绑包) | 5,761(1,312 字节的 Dilithium2 密钥和 2,420 字节的签名) |
| 一个捆绑包证明,层级 14,生产配置 | chain 18 上读取到 1,497,544 和 1,504,260(高度 97,821 处的一次部署和 98,335 处的一次调用,2026-09-30);chain 14 上测得 1,422,116 和 1,423,170(zUSD 注册和一次抵押上架,2026-09-20) |
| 一个授权证明,层级 10,生产配置 | 同样两笔 chain 18 交易中为 1,370,827 和 1,367,303 |
前六行是自 chain 18 起的编码:备注字段为每个信封增加了 512 字节,拆分授权在授权证明自身的字节之前为捆绑包增加了 40 字节。因此,自 chain 18 起(包括 chain 20),无论转移哪种资产,链上的一笔屏蔽转账约为 2.88 MB,基本上全是证明:约 1.5 MB 的捆绑包证明和 1.37 MB 的授权证明。到 chain 15 为止它约为 1.43 MB,只有一个证明。区块上限为 2,000 笔交易(MAX_BLOCK_TXS)以及链的 max_block_bytes 的交易字节数,二者都是共识规则:默认为 4 MiB(MAX_BLOCK_BYTES),只能容纳一笔这样的转账;chain 20 的创世中为 20 MiB,可容纳七笔。带有 hc_auth 的创世必须把 max_block_bytes 设为至少 max_proof_bytes 的三倍再加 1 MiB,因为一笔交易可以携带三个证明(捆绑包、授权、调用),所以 4 MiB 的默认值在那里会被拒绝。区块级聚合是已列入计划的补救办法;参见证明。
回读一笔交易
rand_getTransaction(hash) 在交易提交之前返回 null,提交之后返回其区块位置,且只返回公开字段:
{
"height": 192, "index": 0, "block_hash": "63f6…08",
"tx": {
"hash": "4f2c…e7", "chain_id": 20,
"bundle": {
"anchor": "6b1d…c4",
"nullifiers": ["8c04…d1", "5e77…20", "03aa…6f", "e19b…42"],
"commitments": ["2a9f…07", "b310…88", "77c1…0e", "5d20…b3"],
"fee": "1000000", "burn_a": "0", "burn_r": "0", "burn_asset": 0, "time": 5,
"proof_len": 1497544, "envelope_len": [1860, 1860, 1860, 1860],
"auth_commit": "0bab…e8a1", "auth_proof_bytes": 1370827
},
"action": { "kind": "none" }
}
}
转账中没有 from、to、nonce、金额,也没有 asset 字段,存储的区块里同样没有这些内容:节点已无可再脱敏的信息。自 chain 17 起,转账中包含 auth_commit 和 auth_proof_bytes(认证证明的长度)。捆绑包(bundle)始终有四个槽位,包括虚设(dummy)槽位;RAND 的转账与任何 RPL 代币的转账都是 "kind": "none"。自 chain 14 起,每个 u64 金额都以十进制字符串表示(fee、burn_a、burn_r,以及 action 的 amount);索引、nonce、高度和长度仍为 JSON 数字。其他 action 类型会呈现其公开字段——mint、deploy、call、bond、unbond、withdraw、bridge_attest、bridge_burn、register_token、token_mint、set_authority、token_burn、pause_mints、unpause_mints、register_bridged_token、list_backing、rotate_pq_guardians、rotate_pause_key、四种归属(vesting)类型 claim_vested、revoke_vesting、bond_vested、unbond_vested,以及自 v0.6.8 起的 admit_validator、slash_equivocation、rotate_pq_guardians_v2、rotate_pause_key_v2、cancel_rotation 和 invoke——详见 JSON-RPC 参考。