Эта страница нужна, чтобы построить, отправить или разобрать транзакцию: формат передачи, который принимает rand_sendTransaction, способ вычисления идентификатора, полный список действий с правилами пакетов и комиссий и порядок допуска, определяющий, какую ошибку вы получите первой.
Транзакция
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-слов в порядке little-endian, записанных как 64 строчных шестнадцатеричных символа. Форма передачи — bincode(Transaction) с настройками bincode по умолчанию (целые фиксированной ширины, 8-байтовые префиксы длины, теги перечислений u32); rand_sendTransaction принимает её в виде hex. Подписи над транзакцией и поля отправителя нет: пакет авторизует себя своими доказательствами (доказательство пакета и, начиная с цепочки 17, доказательство авторизации рядом с ним), а подписанное действие несёт подпись внутри себя. Защита от повтора — это множество нуллификаторов для пакетов, одноразовый номер каждого валидатора в реестре для подписанных действий стейкинга, mint_nonce каждого токена для TokenMint и SetAuthority, а также pause_nonce и list_nonce моста для его действий управления.
Транзакция несёт не более одного пакета, и каждое действие, не являющееся беспакетным, несёт ровно один. Слоты 0–1 пакета несут приватный актив, слоты 2–3 — RAND, так что одно доказательство переводит токен и оплачивает его комиссию в RAND; сжигание через два пакета моста из прежней конструкции ушло: сжигание — это один пакет (Ноты).
Кошелёк rand строит всё это через randprotocol_client::wallet::{send, submit}: выбирает входы, получает якорь, вычисляет свидетельства по собственной копии дерева кошелька, строит выходы и запечатывает все четыре конверта, берёт привязку транзакции, строит доказательство пакета и доказательство авторизации, проверяет опубликованный дайджест доказательства пакета, отправляет.
Привязка транзакции
У транзакции нет подписи, а доказательство пакета фиксирует только собственный дайджест пакета: якорь, нуллификаторы, обязательства, комиссию, поля сжигания, time. До цепочки 13 ничто не связывало доказанный пакет с действием, с которым он шёл, или с его конвертами, так что любой, кто видел транзакцию, мог сохранить все байты доказательств и изменить остальное: адрес назначения у BridgeBurn, валидатора у Bond, конверт. Начиная с цепочки 14 каждое доказательство пакета строится над привязкой транзакции и проверяется относительно неё:
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.proof, с цепочки 17 также bundle.auth_proof, и proof у Aggregate. Всё остальное внутри: auth_commit, все четыре конверта, каждое поле сжигания, адрес назначения, валидатор, получатель, подписи, аттестация гаранта и её сопроводительные подписи Dilithium2, а также собственное доказательство Call. Восемь слов передаются в публичном входном сегменте доказательства, дайджест которого H_PUB схема уже связывает, поэтому исправление не потребовало изменений ни в госте, ни в схеме; узел выполняет Machine::verify_public(hc_bundle, binding, proof) и отклоняет доказательство пакета, объявляющее любую другую публичную высоту (public height not the transaction binding's). Transaction::binding — единственная функция, которую кошелёк вызывает перед построением доказательства, а реестр — перед проверкой. С цепочки 17 доказательство авторизации строится над теми же восемью словами и проверяется относительно них, так что его тоже нельзя перенести на другую транзакцию.
Цепочка 20 связывает хеш генезиса. До цепочки 19 привязка и подписанные сообщения беспакетных действий покрывали только идентификатор цепочки, так что две цепочки с одним идентификатором (повторный выпуск, частная копия) приняли бы доказательства и подписи друг друга. Генезис цепочки 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), действий агрегатора и сообщений управления моста. Регистрация валидатора в такой цепочке — всегда сообщение v2 (генезис, цепочка, адрес, выплата). Ничто из доказанного или подписанного для одной цепочки не проходит проверку в другой, каков бы ни был её идентификатор. Слова привязки — публичный вход, поэтому ни ключ гостя, ни ключ верификатора не изменились. Кошелёк старше v0.6.8 по-прежнему строит доказательство по форме с идентификатором цепочки, и в цепочке 20 ему отказывают. rand_getLimits и rand_status отдают binding_domain (1 в цепочке 20, 0 без этого поля).
При генезисном hardening_v6 (цепочки с 16 по 20) собственное доказательство Call тоже связано — вторым дайджестом той же конструкции под собственным доменом, в котором стёрто и доказательство вызова, и доказательства пакета:
call_binding(tx) = blake3("rand-call-bind-1" || bincode(chain_id, bundle', action'')) as 8 LE u32 words
В цепочке 20 тег — rand-call-bind-2, а хеш генезиса открывает прообраз, как сказано выше. Доказательство Invoke связывается так же, с сохранением объявленного перехода в прообразе, так что доказательство фиксирует именно то изменение состояния, выплаты и получателей, которые несёт транзакция.
Доказательство вызова несёт эти слова в своём публичном входном сегменте, после публичного входа программы, заданного при развёртывании, если он есть, поэтому его копия, приложенная к другому пакету комиссии, не проходит проверку. Две привязки вложены друг в друга: кошелёк выбирает ноты пакета комиссии и строит транзакцию с пустыми доказательствами, строит доказательство вызова относительно привязки вызова, вставляет это доказательство и только потом берёт привязку транзакции и строит доказательство пакета.
Вывод для клиента: стройте всю транзакцию — выходы, конверты, действие — до построения доказательств. Кошелёк, собранный до форка, строит доказательство с пустым публичным сегментом, и ему отказывают. У беспакетных действий нет доказательства, которое можно было бы связать, и они держатся на собственных подписях. Привязка — не идентификатор транзакции, он не изменился.
Идентификатор транзакции
txid = blake3("rand-txid-3" || bincode(view))
где view — транзакция, в которой байты proof пакета заменены на blake3(proof) (proof_hash), а байты его auth_proof — на blake3(auth_proof) (auth_proof_hash, после auth_commit, который входит как есть); каждое другое поле берётся без изменений. До цепочки 16 домен был rand-txid-2, без двух полей авторизации; с цепочкой 17 изменился каждый идентификатор транзакции. Хеширование доказательства по дайджесту позволяет усечённому пакету (агрегация блоков, в цепочке 20 не активна) сохранить тот же идентификатор, что и в исходной форме; усечённый пакет сохраняет своё доказательство авторизации. Беспакетная транзакция хеширует свои байты как есть. Один и тот же идентификатор служит ключом для мемпула, квитанций и каждого RPC-запроса.
Все действия
Action — это перечисление bincode, теги идут в порядке объявления:
| тег | действие | поля | пакет | минимальная комиссия |
|---|---|---|---|---|
| 0 | None | — | обязателен | BUNDLE_BASE = 0,001 RAND |
| 1 | Mint | cm, pk, time, r, envelope, amount, minter: PublicKey, signature | должен отсутствовать | 0 (подписывается валидатором; только цепочки с faucet, 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 + 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 реестра (1 RAND в chain 20, сжигается) |
| 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: два подписанных заголовка блока одного лидера для одного вида (view) | должен отсутствовать | 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_a для burn_asset — это то, что поступает в программу | минимальная комиссия вызова + 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. Её генезис содержит секцию bridge с rules_v2, секцию 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 (chain 14, без rules_v2, отклоняла 22 и 23). SlashEquivocation требует секции staking.slashing, а в chain 20 она равна null. Пять действий агрегации требуют секции aggregation, которой в chain 20 нет (сборка отказывается запускаться на генезисе, который её содержит, пока агрегация не будет измерена заново). Узел отклоняет все эти действия на шлюзе, раньше любой другой проверки, но они по-прежнему занимают свои теги bincode, поэтому кодировщик, в котором номера тегов заданы жёстко, обязан их учитывать. Агрегация описана как предстоящая в разделе Proofs. Четыре действия генезисного вестинга (теги с 24 по 27, в сборке начиная с v0.5.11) требуют секции vesting, которой не было ни в одной цепочке, включая chain 20, и без неё отклоняются при допуске. Теги с 28 по 33 появились в v0.6.8 и добавлены в конец, поэтому история каждой более ранней цепочки по-прежнему декодируется.
Четыре действия RPL (теги с 14 по 17) регистрируют токен, выпускают дополнительное количество токена с правом выпуска по Key, передают или слагают право выпуска и сжигают имеющийся остаток; RegisterToken с полномочием Bridge или Program отклоняется. Действия для перевода токенов нет: перевод токена — это None, неотличимый от платежа в RAND. См. RPL tokens. Четыре действия управления мостом (теги с 18 по 21) — это приостановка выпуска, её снятие и листинг bridged-токена или дополнительного обеспечения после генезиса. PauseMints подписывается генезисным ключом приостановки, который может только приостанавливать; остальные три требуют кворума PQ-хранителей на Dilithium2. BridgeAttest также несёт такой кворум рядом с кворумом ECDSA. Теги 22 и 23 при rules_v2 моста заменяют набор PQ-хранителей или ключ приостановки, каждый под PQ-кворумом текущего набора и с rotation_nonce моста. Теги 30 и 31 делают то же при bridge.rotation и дополнительно несут собственную подпись Dilithium2 каждого нового ключа под сообщением ротации — доказательство владения ключом; в chain 20 ротация фиксируется как ожидающая и вступает в силу после delay_secs, 86 400 с. CancelRotation (тег 32), подписанный текущим ключом приостановки, отменяет ожидающую ротацию до её вступления в силу. См. Bridge.
AdmitValidator (тег 28) — это голосование набора валидаторов за допуск нового ключа: подписи валидаторов, держащих более двух третей стейка, в порядке возрастания адресов. При staking.admission_by_vote (chain 20) Bond, который зарегистрировал бы нового валидатора, отклоняется, если его адрес не был допущен таким образом (Staking). Invoke (тег 33, RPL-2) — это вызов, чьё доказательство ручается за одно объявленное transition состояния программы: прочитанные и записанные ячейки, то, что поля сжигания пакета вносят в хранилище программы, и ноты, которые хранилище выплачивает или которые программа выпускает. Реестр применяет переход, если каждое объявленное чтение по-прежнему верно и оба доказательства проходят проверку (Programs).
Deploy и Call — обычные экранированные транзакции, пакет которых представляет собой перевод самому себе на нулевую сумму: он нужен, чтобы оплатить минимальную комиссию из собственных нот вызывающего, так что цепочка никогда не узнаёт, кому они принадлежали. RAND покидает пул только через burn_r — стейк Bond, залог RegisterAggregator, депозит Invoke в хранилище программы, — а токен только через burn_a, где burn_asset называет его, в TokenBurn, BridgeBurn или Invoke. Любое другое действие ничего не сжигает, а пакет с burn_asset = 0 и ненулевым burn_a отклоняется везде.
Порядок допуска
Ledger::validate_inner проверяет дешёвое раньше дорогого, в таком порядке; мемпул выполняет ту же проверку до рассылки по gossip, так что плохая транзакция отклоняется один раз, на границе.
- Ограничения размера — вся транзакция ≤
max_block_bytesцепочки; каждый из четырёх конвертов пакета и любой конверт ноты, который несёт действие, ровноenvelope_bytesиз genesis (1 860 в chain 20; не более 2 048 байт в цепочке без этого поля); доказательство пакета, доказательство авторизации и доказательство вызова — каждое ≤max_proof_bytes(по умолчанию 2 MiB, 4 MiB в genesis chain 20; в цепочках с 13 по 16 было 8 MiB); программа ≤ предела программы цепочки (по умолчанию 4 096 слов, 65 535 в genesis chain 20); конверт вызова ≤max_call_envelope_bytes(по умолчанию 18 432 байта, 65 536 в chain 20); аттестация ≤ 16 KiB. Доказательство пакета в запечатанной (усечённой) маркерной форме здесь отклоняется вне синхронизации в запечатанной форме (PrunedFormOutsideSync), до любой работы с доказательством. - Идентификатор цепочки совпадает.
- Форма и нижняя граница комиссии — действие без пакета не несёт пакета, а все остальные несут ровно один;
check_burn_shapeопределяет, какое из трёх полей сжигания действие вправе задать:TokenBurnилиBridgeBurnсжигает собственныеassetиamountчерезburn_assetиburn_a,BondилиRegisterAggregatorсжигает свою сумму черезburn_r, а любое другое действие ничего не сжигает;fee ≥ fee_floor(action). В цепочке с разделомgas(chain 20) нижняя граница дляCallилиInvokeна этом шаге — правило газа при заявленном лимите 1:BUNDLE_BASE + gas_price + byte_price × ⌈bytes / 1024⌉по его доказательству и конверту входных данных, которые известны до любой проверки. - Якорь — один из последних 1 024 корней конца блока в chain 20, genesis которой задаёт
proof_window_blocks: 1024(v0.6.8); один из последних 256 в цепочке без этого поля, как до chain 19 включительно. - Время — в пределах
[height − 1024, height]в chain 20;[height − 256, height]без этого поля. Единственное значение в genesis задаёт оба окна. - Нуллификаторы и обязательства — все четыре нуллификатора различны и ни один не потрачен; все четыре обязательства различны и ни одно ещё не является листом.
- Проверки действия — кран включён, выпуск не превышает предел и подписан валидатором, а в chain 20 указывает получателя из списка
staking.faucet_recipientsв genesis, укладывается вfaucet_budget_per_epochэпохи и подписан валидатором из спискаstaking.faucet_mintersв genesis (правило допустимости с chain 16); выпуск или депозит от 2⁶³ и выше отклоняется в любой цепочке; программа декодируется; программа существует; правила стейкинга (регистрация присутствует ровно тогда, когда валидатор неизвестен, нонс, подпись, достаточный стейк); собственные правила моста дляBridgeAttestиBridgeBurnи для действий приостановки и листинга; правила RPL для его четырёх действий, включая плату за регистрацию; в chain 20 кворум голосования за допуск, а дляInvoke— форма перехода, заявленные чтения относительно сохранённых ячеек, покрытие хранилищем каждой выплаты и плата за ячейку. Всё это выполняется до доказательства пакета, поэтому транзакция, которую нельзя применить, не стоит ни одной проверки STARK. Приhardening_v6(цепочки с 16 по 20) здесь стоят ещё два отказа:Deployпрограммы, которую не мог бы удерживать ни один вызов (ProgramUncallable), и доказательство пакета или вызова, в заголовке или транскрипте которого есть поле, которого честный доказывающий не записал бы (non-canonical proof: …), — оба отклоняются до проверки любого из доказательств. Начиная с v0.6.8 доказательство, заголовок которого объявляет степень выше потолка самой высокой таблицы, отклоняется первым с тем же вердиктом (аудит v6 ZKV-4), до того как проверка начнёт его разбирать. - Дайджест пакета — в цепочке с разделённой авторизацией (каждая цепочка с chain 17) сначала проверяются поля авторизации: пакет без доказательства авторизации отклоняется, значение
c, которое публикует доказательство авторизации (прочитанное, но ещё не проверенное), должно равнятьсяauth_commit, а в chain 20 доказательство авторизации должно объявлятьGAS_LIMITровно 1 279. Затем реестр пересчитывает дайджест опубликованных полей пакета, включаяauth_commit, и он должен совпасть с тем, что опубликовало доказательство. В chain 20 доказательство пакета также должно объявлятьGAS_LIMITровноbundle_gas_limitиз genesis, 20 479. - Доказательство пакета —
Machine::verify_publicотносительно закреплённого в genesishc_bundleи привязки транзакции. В цепочке с разделённой авторизацией следом проверяется доказательство авторизации — относительно закреплённого в genesishc_authи той же привязки. - Доказательство вызова и его комиссия по уровню — для
CallилиInvokeсначала фиксируется заголовок доказательства (уровень не выше 14, таблица keccak не больше 2¹², таблица sha256 не больше 2¹³, высота развёрнутой программы, с нижней границей 2⁷ строк приhardening_v6, а в chain 20 объявленныйGAS_LIMITне больше потолка заголовкаgas_max), затем STARK вызова относительноcode_hashпрограммы и, приhardening_v6, привязки вызова транзакции. Комиссия проверяется последней, потому что она становится известна только после декодирования доказательства: в chain 20fee ≥ BUNDLE_BASE + gas_price × GAS_LIMIT + byte_price × ⌈bytes / 1024⌉по действующим ценам, плюсcell_feeза каждую созданную ячейку дляInvoke(Комиссии); в цепочке без разделаgas—fee ≥ BUNDLE_BASE + call_fee(tier).
Ошибки возвращаются из rand_sendTransaction с кодом -32000 и причиной: nullifier already spent, anchor is not one of the last 1024 roots (256 в цепочке без proof_window_blocks), 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 | 1 892 в закодированном виде (1 860 шифртекста) с chain 18; 1 380 (1 348) до chain 17 включительно |
Bundle без двух его доказательств | 7 936 (четыре нуллификатора, четыре обязательства, четыре конверта, auth_commit) |
| транзакция перевода без двух её доказательств | 7 949 |
| транзакция развёртывания без двух её доказательств, программа из 100 слов, без публичного входа | 8 369 |
| транзакция вызова без трёх её доказательств, без конверта входных данных | 7 990 |
| транзакция выпуска (без пакета) | 5 761 (ключ Dilithium2 в 1 312 байт и подпись в 2 420 байт) |
| доказательство пакета, уровень 14, производственный профиль | 1 497 544 и 1 504 260, считанные в chain 18 (развёртывание на высоте 97 821 и вызов на высоте 98 335, 2026-09-30); 1 422 116 и 1 423 170, измеренные в chain 14 (регистрация zUSD и листинг обеспечения, 2026-09-20) |
| доказательство авторизации, уровень 10, производственный профиль | 1 370 827 и 1 367 303 в тех же двух транзакциях chain 18 |
Первые шесть строк — кодирование с chain 18: поле memo добавило 512 байт к каждому конверту, а разделённая авторизация добавила 40 байт к пакету перед собственными байтами доказательства авторизации. Поэтому экранированный перевод в сети занимает около 2,88 MB с chain 18, включая chain 20, и почти целиком это доказательства, какой бы актив он ни перемещал: около 1,5 MB доказательства пакета и 1,37 MB доказательства авторизации. До chain 15 включительно он занимал около 1,43 MB, с одним доказательством. Блоки ограничены 2 000 транзакций (MAX_BLOCK_TXS) и max_block_bytes цепочки по байтам транзакций — оба предела являются правилами консенсуса: по умолчанию 4 MiB (MAX_BLOCK_BYTES), что вместило бы один такой перевод, и 20 MiB в genesis chain 20, где помещается семь. Genesis с 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, и в сохранённом блоке их тоже нет: узлу больше нечего скрывать. Поля auth_commit и auth_proof_bytes (длина доказательства авторизации) присутствуют начиная с chain 17. В пакете всегда четыре слота, включая фиктивные, а перевод RAND и перевод любого токена RPL имеют одинаковое значение "kind": "none". Начиная с chain 14 каждая сумма u64 представлена десятичной строкой (fee, burn_a, burn_r, поле amount действия); индексы, nonce, высоты и длины остаются числами JSON. Остальные виды действий отображают свои публичные поля — 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.