Rand Protocol
Документация
Справочник

JSON-RPC

Все методы rand_*, которые обслуживает узел: параметры, форматы результатов и ошибки, а также пакетные запросы, подписки WebSocket и формат транзакций в сети.

Узел обслуживает JSON-RPC 2.0 по HTTP на --rpc (по умолчанию 127.0.0.1:8545), а также WebSocket на том же порту для подписок. Начиная с v0.6.8 он может открыть и второй, публичный listener (--public-rpc, ниже). randprotocol_client::RpcClient (Rust) оборачивает каждый из перечисленных ниже методов. Эта страница — список методов в том виде, в каком их отдаёт v0.6.8 (c9c9bd3c), сборка, на которой работает chain 20; если метод есть в коде, но на chain 20 не действует, об этом сказано.

curl -s http://127.0.0.1:8545 -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"rand_getHead","params":[]}'

Публичная конечная точка

https://rpc.randprotocol.org (тот же прокси доступен и по адресу https://randprotocol.org/api/rpc) перенаправляет запросы на архивный узел chain 20, который хранит каждый блок, — для кошельков и страниц, у которых нет собственного узла (rand --rpc https://rpc.randprotocol.org …). Принимается только HTTP POST, CORS открыт, WebSocket нет, и по возможностям она уже, чем узел:

  • Один запрос за вызов. Пакет отклоняется с кодом -32600.
  • Список разрешённых методов: с 2026-10-01 тот же набор, что и у публичного listener самого узла (rpc::PUBLIC_METHODS в v0.6.8), поэтому в нём есть чтение состояния программ, которое нужно rand program invoke, rand_getAdmitted, чтение агрегации и чтение вестинга, а также rand_checkTransaction и rand_sendTransaction. Методов rand_mint, rand_importViewingKey, rand_getViewingNotes, rand_removeViewingKey и rand_getPeers в нём нет, и они отвечают -32601 method not allowed here, не доходя до узла: публичный faucet — поверхность для злоупотреблений, а viewing key следует передавать только узлу, который запускаете вы сами.
  • Тело запроса — не более 12 MiB: этого хватает для любого перевода, действия с токеном или развёртывания, а также для вызова с доказательством до примерно 3,4 MB (транзакция кодируется в hex, а доказательства bundle и auth занимают около 2,85 MB ещё до доказательства вызова). Более крупный вызов отправляйте на собственный узел. При превышении лимита: HTTP 413 и -32600.
  • На адрес — 1 200 запросов в минуту и 6 в минуту для rand_sendTransaction. При превышении: HTTP 429 и -32005. Бюджет на чтение рассчитан на первую синхронизацию кошелька — несколько сотен запросов одной серией (645 на высоте 99 000 в chain 18).
  • -32000 upstream unavailable, если узел за прокси не отвечает.

Всё остальное на этой странице описывает сам узел.

Два listener’а узла

Начиная с v0.6.8 (audit v6) у узла два listener’а JSON-RPC.

  • --rpc <ADDR>, listener оператора. Все методы, пакеты и WebSocket. Он определяет вызывающего по адресу сокета и доверяет loopback: методы viewing key отвечают только на loopback, а loopback не учитывается в лимитах. Держите его на 127.0.0.1. Обратный прокси или SSH-переадресация приходят через loopback, поэтому за ними каждый посетитель выглядит как оператор: никогда не направляйте публичную конечную точку на этот порт.
  • --public-rpc <ADDR>, публичный listener, предназначенный именно для такого звена. Он обслуживает фиксированный набор методов: чтение, которое нужно кошельку, эксплорер или странице, и rand_sendTransaction (rpc::PUBLIC_METHODS). rand_importViewingKey, rand_getViewingNotes, rand_removeViewingKey, rand_mint и rand_getPeers на нём не обслуживаются никогда, как и любой метод, добавленный позже, пока он не внесён в список; такой вызов отвечает -32000 <method> is not served on this node's public listener. Пакет — это -32600, маршрута WebSocket нет. Один счётчик охватывает всех вызывающих вместе: 600 запросов в пике и 150 в секунду; сверх этого — HTTP 429 с телом -32000. Лимиты на посетителя относятся к прокси, который видит посетителей. Его вызовы занимают не более половины слотов блокирующего чтения узла, поэтому listener оператора никогда не остаётся без ресурсов.

Оба listener’а разделяют два ограничения на чтение. Блокирующее чтение, не завершившееся за 30 s, получает ответ -32000 this read took longer than 30 s on this node; retry with a smaller range. rand_getBlocks заканчивает страницу, прочитав 64 MiB блоков.

--rpc-viewing-token-file <PATH> устанавливает bearer-токен на методы viewing key для --rpc: первая строка файла, не менее 32 символов, передаваемая как Authorization: Bearer <token>. При этом одного loopback недостаточно, и обслуживается любой вызывающий, который предъявит токен, откуда бы он ни обращался.

Соглашения

  • Адреса валидаторов — строки base58 (32 байта). Хеши — 64 шестнадцатеричных символа, с 0x или без. Shielded-значения — обязательства, нуллификаторы, anchor’ы, корни деревьев, уровни свидетельств — имеют тип Word8: восемь little-endian слов u32 в виде 64 шестнадцатеричных символов в нижнем регистре.
  • Shielded-адреса имеют вид rand1 + base58, 1 667 символов. Параметр длиннее 2 000 символов отклоняется по длине ещё до разбора.
  • Каждая сумма u64 — десятичная строка ("1500000000" = 1,5 RAND; 1 RAND = 10⁹ единиц): единицы RAND или единицы токена, состояние цепочки или собственное поле декодированной транзакции. JSON-число не является точным целым после 2⁵³, а предложение токена с 9 знаками после запятой переходит эту границу на нескольких десятках миллионов единиц. Что это изменило, см. в датированной заметке ниже.
  • Индексы, nonce, высоты, индексы листьев, view, длины, число знаков после запятой и счётчики дней (mint_day) никогда не являются суммами и везде передаются как целые числа JSON.
  • В каждом запросе должен быть член id. Объект без него является уведомлением и отклоняется с -32600; явный "id": null — обычный запрос.
  • Метода баланса и метода аккаунта нет. Кошелёк вычисляет баланс, сканируя rand_getCommitments и rand_getNullifiers своим viewing key. rand_getBalance, rand_getAccount и rand_getAssetBalance отвечают -32601.

Критическое изменение, 2026-09-20 (chain 14): каждая сумма — десятичная строка

До chain 14 суммы внутри декодированной транзакции были целыми числами JSON. Это исключение убрано. Следующие поля изменились с чисел JSON на десятичные строки, без иных изменений в значении или положении:

  • bundle.fee — в tx метода rand_getTransaction и в transactions[] методов rand_getBlockByHeight / rand_getBlockByHash;
  • action.amount для kind = mint, bond, unbond, withdraw, bridge_attest (те же три метода);
  • action.amount и action.relayer_fee для kind = bridge_burn (те же три метода);
  • locked в строках rand_getAssets и rand_getBridgeState.assets[].

Клиент, читающий любое из этих полей как число, должен разбирать строку. aggregation.subsidy_base в rand_status (только для цепочек с агрегацией) стало строкой в том же изменении. Все остальные суммы уже были строками: rand_getSupply, rand_getEmission, rand_getValidators, viewing notes и каждое поле, появившееся вместе с chain 14, — burn_a и burn_r в bundle, предложения токенов, locked каждого обеспечения, mint_cap_per_day и minted_today, registration_fee, а также amount в token_mint, token_burn и register_token (initial_amount, initial.amount).

Пакеты

Тело POST может быть массивом из не более чем 20 объектов запросов; в ответ приходит массив той же длины в порядке запросов, включая ошибки. Запросы выполняются один за другим. Пустой массив, массив длиннее 20 или тело, которое не является ни объектом, ни массивом, дают один ответ -32600 с null в id. Всё тело по-прежнему ограничено лимитом тела запроса узла, рассчитанным на одну транзакцию с доказательством: пакет из двух вызовов rand_sendTransaction с доказательствами отклоняется с 413. Объединяйте в пакеты чтения, а не отправку транзакций с доказательствами.

Цепочка и узел

rand_chainId

Параметры []. Результат: идентификатор цепочки (целое число); 20 в testnet.

rand_tokenInfo

Параметры []. Результат { "symbol": "RAND", "decimals": 9 }.

rand_getHead

Параметры []. Результат { "height": 1998, "hash": "…", "view": 2251 }. view — текущий консенсусный view узла, который опережает высоту, когда views завершаются по тайм-ауту.

rand_status (псевдоним rand_syncStatus)

Параметры []. Результат:

{
  "height": 1998, "head_hash": "…", "view": 2251, "high_qc_view": 2250,
  "syncing": false, "sync_target": 1998,
  "sync_inflight_age_ms": null, "sync_failures": 0, "sync_late_batches": 0,
  "peer_count": 26, "connected_peers": 26, "reserved_peers": 25, "ws_clients": 0, "refused_cache": 0,
  "verify_queue": 0, "mempool_size": 0,
  "is_validator": true, "active_validator": true, "faucet": true, "confidential": true,
  "testnet": true, "fri_profile": "production", "programs": 10, "viewing_keys": 0,
  "notes": 197, "nullifiers": 160, "tree_root": "72b9…1197", "hc_bundle": "60af…3fce", "hc_auth": "1e4e…39c1",
  "binding_domain": 1,
  "address": "5TAofR…", "peer_id": "12D3KooW…",
  "gas_prices": { "gas_price": "100", "byte_price": "800" }
}

is_validator: у этого узла есть ключ валидатора; active_validator: этот ключ входит в набор, работающий в текущей эпохе. reserved_peers (v0.6.8) — число пиров, допущенных сверх лимита входящих соединений: bootstrap-узлы, любые --reserved-peer и каждая идентичность валидатора, узнанная из подписанной привязки пира, по одному разу; на узле флота оно должно достигать числа валидаторов в течение минуты после запуска. viewing_keys — сколько viewing keys узел держит в памяти для сканирования на стороне узла. verify_queue — число транзакций, ожидающих одного из четырёх воркеров проверки доказательств (не более 64 в очереди). hc_bundle — guest bundle, относительно которого построены доказательства этой цепочки, а hc_auth — guest auth, закреплённый её genesis (раздельная авторизация, начиная с chain 17), или null в цепочке без него; узел, чья сборка не совпадает с любым из них, отказывается запускаться. testnet и binding_domain — одноимённые поля genesis, как их отдаёт rand_getLimits. gas_prices — текущие цены газа из реестра tip, обновляемые при каждом коммите, в виде десятичных строк; null в цепочке без раздела gas. Цены chain 20 динамические и меняются от блока к блоку в зависимости от заполненности, никогда не опускаясь ниже минимумов 100 и 800 и не поднимаясь выше максимумов 10 000 и 80 000, поэтому читайте их здесь или в rand_getLimits и не прописывайте в коде. Объект также содержит объект aggregation (его subsidy_base — десятичная строка, остальное — целые числа), все значения которого равны нулю в цепочке без раздела aggregation, как и chain 20.

Объект также содержит disk_free_bytes (число) и disk_low, а также пару полей хранения: prune_floor — самую низкую высоту, которую узел ещё хранит (0 на архивном), и prune_history_secs — окно, которое хранит узел, запущенный с --prune-history (null, если он хранит всё). В chain 20, как и в chain 19, 24 из 26 валидаторов хранят блоки за 24 часа; два архивных хранят все. См. Pruned nodes.

rand_getPeers

Параметры []. Результат: массив { "peer_id": "12D3KooW…", "addrs": ["/ip4/…/tcp/30303"], "connected_secs": 1241 }.

rand_getBlockByHeight / rand_getBlockByHash

Параметры [height] или [hash]. Результат null, если блок неизвестен, иначе:

{
  "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": [ ]
}

Отдаются только закоммиченные блоки. Поле transactions имеет ту же структуру, что и tx в rand_getTransaction. Блок также содержит sealed, а каждая его транзакция — sealed_by; в цепочке без агрегации они равны false и null.

rand_getBlocks

Параметры [from_height, to_height]. Результат: заголовки блоков, от старых к новым, не более 1 024 начиная с from_height (128 на узле до 2026-09-24): поля rand_getBlockByHeight без transactions. Диапазон шире лимита или выходящий за голову цепочки обрезается, а не отклоняется, и начиная с v0.6.8 страница также заканчивается, как только прочитано 64 MiB блоков; продолжайте с последней возвращённой высоты. -32602, если to_height меньше from_height.

Начиная с v0.6.8 каждый заголовок также содержит public_notes: транзакции блока bridge_attest, token_mint и register_token, а в цепочке с комиссиями моста и его транзакции bridge_burn, в виде [{ "hash", "raw" }] (кодировка, которую отдаёт rand_getRawTransaction); для всех остальных блоков поле пусто. Сжигание отдаётся с очищенными доказательствами и "proofs_stripped": true; его hash — настоящий идентификатор транзакции. Первая синхронизация кошелька читает эти страницы, и больше ему ничего не нужно, чтобы восстановить публичные ноты.

rand_getFinality

Параметры [height] или [hash]. Результат — один из { "status": "committed", "height", "hash" }, { "status": "certified", "height", "hash", "qc_view" }, { "status": "proposed", "height", "hash" }, { "status": "unknown" }. Высота проверяется только по закоммиченной цепочке, поэтому ответ для неё — committed или unknown; хеш может дать и certified (в неподтверждённом дереве реплики, с сертификатом кворума), и proposed (в дереве, но без него).

rand_getProposer

Параметры [view] или [from_view, to_view], не более 64 view. Результат { "epoch": 3, "proposers": [ { "view": 2251, "proposer": "2nRdFC…" } ] }: лидер каждого view при текущем наборе валидаторов, а не при том наборе, который работал в этом view в прошлом. -32602 для диапазона в обратном порядке или более чем 64 view.

rand_getVersion

Параметры []. Результат { "version", "git_sha", "chain_id", "hc_bundle", "hc_auth", "fri_profile" }. version — версия крейта workspace; git_sha — хеш коммита, зафиксированный при сборке, с добавленным -dirty, если отслеживаемые файлы отличались, либо "unknown". hc_auth — гостевая программа аутентификации из genesis, как в rand_status, либо null. Публичная конечная точка 2026-10-01, цепочка 20:

{ "version": "0.6.8", "git_sha": "c9c9bd3c1903aa4cc5833b7ab8c62fc3e6942017", "chain_id": 20,
  "hc_bundle": "60af094acfe65d85fdb18fb3d06cf9085dcf28c96e59e87f1ee527226e6e3fce",
  "hc_auth": "1e4e347f44cf86750b30a9a4bdf9ec9256efe353d4ff8017451eca7d195639c1",
  "fri_profile": "production" }

rand_getGenesisHash

Параметры []. Результат: хеш genesis в шестнадцатеричном виде; "6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135" для цепочки 20 (у цепочки 19 был a3defc93…228a). Один лишь идентификатор цепочки не позволяет отличить два среза цепочки; этот хеш позволяет. В цепочке 20 он также входит в привязку каждой транзакции и в каждое подписанное сообщение действия (binding_domain: 1, см. rand_getLimits).

rand_getHealth

Параметры []. Результат — один из { "status": "disk_low", "free_bytes": "3221225472" }, { "status": "ok" }, { "status": "syncing", "behind": 412 }, { "status": "behind", "behind": 30 }. disk_low имеет приоритет над всеми остальными, когда свободное место в файловой системе каталога данных меньше четырёх значений --min-free-disk-mb узла (по умолчанию 1024, то есть меньше 4 GB); free_bytes — десятичная строка. ok — когда sync_target - height не больше 2 и нет незавершённых запросов пакета синхронизации; syncing — пока такой запрос выполняется; behind — когда узел не синхронизируется, а отставание всё ещё больше 2.

rand_getMempoolInfo

Параметры []. Результат { "count": 2, "bytes": 2611200, "oldest_ms": 1450, "max_count": 10000 }. bytes суммирует закодированную длину всех транзакций в пуле; oldest_ms равно null, когда пул пуст. Начиная с v0.6.8 пул ограничен ещё и в байтах — восемью max_block_bytes из genesis (160 MiB в цепочке 20): сверх этого предела транзакция, платящая больше сверх своего минимума за KiB, вытесняет самую дешёвую, а платящая меньше или столько же получает отказ mempool full. Действия управления проходят оба лимита.

Отправка

rand_sendTransaction

Параметры [hex], где hex — это bincode(Transaction). Результат: хеш транзакции. Узел проверяет её в порядке допуска (Транзакции), помещает в пул и рассылает по сети. Ошибки — -32000 с указанием причины: nullifier already spent, anchor is not one of the last 1024 roots, time 12 is outside [1500, 2524] (оба окна равны proof_window_blocks из genesis, 1 024 в цепочке 20; 256 в цепочках до 19 включительно), 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; для invoke — cell <key> is no longer what this transition read (вердикт по состоянию: перечитайте ячейку и докажите заново) или the vault holds … of asset … and the transition pays …; для регистрирующего Bond при допуске по голосованию — validator … has not been admitted by the validator set's vote (staking.admission_by_vote); ask the validators for an AdmitValidator first; для бандла, не подходящего под genesis цепочки 20, — envelope is 1348 bytes, this chain requires exactly 1860, 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, auth proof: …, bundle proof declares gas limit …, this chain requires exactly 20479, non-canonical proof: … — re-prove with an up-to-date wallet; для действий моста — bridge: attestation already consumed, the attestation names a different recipient, the attestation deposits under asset 2, and the transaction names 1, the bundle burns 399, not the 400 the action declares; для бандла со сжиганием, которого его действие допускать не может, — the bundle burns asset 2 on an action that burns no token, a burn of 5 is not allowed on this action или RAND burned through burn_a (5); a RAND burn goes through burn_r; для состояния моста — bridge minting is paused, mint cap … per backing per day, already paused, not paused, wrong pause nonce, wrong list nonce, chain N has no registered emitter; для реестра токенов — wrong mint nonce и wrong token index. Приём — это ещё не фиксация: опрашивайте rand_getTransaction или rand_getTransactionStatus.

Транзакция, превышающая лимит блока цепочки (max_block_bytes из rand_getLimits), отклоняется с -32000 и указанием обоих размеров ещё до попадания в мемпул. Тело запроса ограничено значением 2 × (2 × max_proof_bytes + 4 × 2048 + max_call_envelope_bytes + 16,384 + 64 KiB) + 256 KiB, которое вычисляется из genesis при запуске: 8 867 840 байт в цепочке по умолчанию и 17 350 656 при лимитах цепочки 20, как и в цепочках с 17 по 19 (34 127 872 в цепочках с 13 по 15, у которых предел доказательства составлял 8 MiB). 4 × 2048 — это четыре конверта бандла при предельном размере конверта. Тело сверх этого предела получает -32600 с указанием лимита.

Вердиктом о транзакции являются только -32000 (допуск её отклонил) и -32602 (её не удалось декодировать). Любой другой сбой, включая -32603, оставляет её судьбу неизвестной: узел помещает транзакцию в пул и рассылает её до ответа, поэтому узел, остановившийся в этот момент, мог принять то, о чём так и не ответил. randprotocol_client считает отправку отклонённой только по этим двум кодам.

rand_getTransactionStatus

Параметры [[hash, …]], от 1 до 64 хешей. Результат: по одной записи на каждый хеш, в том же порядке:

[ { "hash": "…", "status": "committed", "height": 1998, "index": 0 },
  { "hash": "…", "status": "pending" },
  { "hash": "…", "status": "rejected", "reason": "bad mint signature" },
  { "hash": "…", "status": "unknown" } ]

Порядок поиска для каждого хеша: закоммиченные (хранилище), ожидающие (мемпул), отклонённые (кеш отказов, который считает refused_cache в rand_status), иначе unknown. К rejected относятся только отказы, касающиеся собственных байтов транзакции, которые ни один последующий блок изменить не может: недействительное доказательство, дайджест бандла, не совпадающий с опубликованным его доказательством, неверная подпись минта, неверный идентификатор цепочки, слишком большая часть, повторяющийся внутри транзакции нуллификатор или обязательство, конверт неверного размера, отсутствующее или несовпадающее доказательство аутентификации, бандл или доказательство аутентификации с неверным лимитом газа. Отказ, зависящий от состояния узла в данный момент, — потраченный нуллификатор, anchor или time вне окна, комиссия ниже минимума (который в цепочке 20 меняется вместе с ценами на газ), неизвестная программа, nonce регистра, устаревшее чтение invoke — не запоминается и читается как unknown. -32602 для пустого списка или более чем 64 хешей.

rand_mint (кран testnet)

Параметры [address] или [address, amount]; address — адрес rand1…, amount — строка в единицах, не более 100000000000 (100 RAND, значение по умолчанию). Результат: хеш транзакции минта. Только в цепочке, genesis которой содержит "faucet": true; иначе -32000 faucet is disabled on this chain. Наблюдатель отвечает faucet mints are signed by validators; ask a validator node.

В цепочке, genesis которой задаёт staking.faucet_recipients, как у цепочки 20 (16 кошельков), платить можно только перечисленным кошелькам: минт на любой другой адрес отклоняется с the faucet may not mint to this recipient (not in staking.faucet_recipients). Genesis цепочки 20 также задаёт staking.faucet_minters (18 ключей валидаторов): подписывать минт может только ключ из списка. У крана есть и бюджет на эпоху, staking.faucet_budget_per_epoch (10 000 RAND на эпоху в 1 000 блоков в цепочке 20); минт сверх него отклоняется с FaucetBudgetExhausted — вердиктом по состоянию, который читается как unknown, как только покидает пул.

Faucet ограничен по частоте на уровне процесса узла: восемь минтов подряд, с пополнением по одному в секунду. Сверх этого вызов отвечает faucet is rate limited on this node (8 mints back to back, refilling at 1/s); try again shortly. Поэтому поток запросов к faucet не может заполнить пул раньше bridge PauseMints, который, как и остальные три действия управления мостом, также освобождён от отказа пула mempool full и упорядочивается в блоке первым.

Начиная с chain 14 минт раскрывается: действие Mint несёт pk, time и r ноты рядом с cm и amount, при приёме commitment пересчитывается, и cm, не раскрывающийся в них, отклоняется (the mint's commitment does not open to its published note and amount), а minter подписывает все семь полей в домене rand-mint-2; на chain 20 (binding_domain: 1) — в rand-mint-3, с хешем genesis в начале. Владелец и сумма ноты faucet поэтому публичны, как и выплата withdraw. Параметры rand_mint не изменились.

rand_estimateFee

Параметры [spec], один из {"kind":"bundle"}, {"kind":"deploy","words":n,"public_words":m}, {"kind":"call","tier":t,"bytes":b,"gas":g}, где t равно 10, 12, 14, 16, 18, 20. Результат: минимальная комиссия в единицах, строкой. Для {"kind":"bundle"} это "1000000". public_words (необязательно, по умолчанию 0) оплачивается за слово, как код, поэтому комиссия равна комиссии за развёртывание n + m слов. bytes (необязательно, по умолчанию 0) — длина доказательства вызова плюс длина его входного конверта.

Как оценивается вызов, зависит от gas_metering в rand_getLimits:

  • "circuit", цепочка с собственной секцией gas, как chain 20. gas — лимит gas, который объявляет доказательство вызова, и он обязателен: без него вызов отвечает -32602 under the gas section a call estimate needs its gas. Ответ равен 1,000,000 + gas_price × gas + byte_price × ⌈bytes / 1024⌉ по текущим ценам на вершине цепочки, тем, что отдаёт rand_getLimits. При минимальных ценах chain 20, 100 и 800, вызов, объявляющий 20,479 gas при 1,300,000 байт доказательства и конверта, стоит "4063900". Цены меняются с каждым блоком, поэтому оценивайте незадолго до отправки.
  • "header", политика gas на уровне узла в цепочке без этой секции. Спецификация вызова принимает keccak_log_height и sha256_log_height (необязательно, по умолчанию 0, каждый от 0 до 40, иначе -32602) — высоты хеш-таблиц, которые объявляет заголовок доказательства, и ответом служит большее из минимума яруса в ledger и минимума gas для этого заголовка. При наличии секции gas обе высоты принимаются, но не используются.
  • null, ни то ни другое. Только расписание ярусов ledger: в пределах бесплатной квоты (2,097,152 + 18,432 байт) вызов стоит комиссию своего яруса; каждый KiB сверх неё, причём неполный KiB считается за целый, добавляет 1,000 единиц.

Начиная с v0.6.8, {"kind":"invoke", …, "created_cells": c} оценивает invoke RPL-2: все поля спецификации вызова плюс created_cells (необязательно, по умолчанию 0, не более max_writes, иначе -32602) — ячейки, которые создал бы переход. Каждая добавляет cell_fee цепочки (rand_getLimits.program_state). Их считает вызывающий: это запись ненулевого значения в ячейку, которую rand_getProgramCell читает как нули. На chain 20 вызов выше с двумя созданными ячейками стоит "24063900". В цепочке без секции program_state этот вид даёт -32602.

Комиссии содержат расписание.

Пул

rand_getTreeInfo

Параметры []. Результат { "next_index": 41, "root": "6b1d…c4", "nullifiers": 12 }: число листьев, текущий корень, сколько нот потрачено.

rand_getCommitments

Параметры [from_index] или [from_index, limit]. Результат: страница листьев начиная с from_index, сначала старые, не более 1,000 строк при любом limit.

[ { "index": 40, "cm": "2a9f…07", "height": 37,
    "envelope": { "kem_ct": "b41c…", "to_receiver": "77e0…", "to_sender": "0c31…", "body": "9dd2…" } } ]

rand_getNullifiers

Параметры [from_height] или [from_height, limit]. Результат: все nullifier, опубликованные начиная с этой высоты, с тем же пределом в 1,000 строк. Страница может оборваться внутри высоты; возвращайтесь к наибольшей из уже виденных высот.

[ { "height": 37, "nullifier": "8c04…d1" }, { "height": 41, "nullifier": "12be…9a" } ]

rand_getCompactBlocks

Параметры [from_height, to_height], оба обязательны. Результат: одна строка на блок, сначала старые, с тем, что нужно лёгкому кошельку, и ничем больше:

[ { "height": 192, "hash": "63f6…08", "timestamp_ms": 1788000123456,
    "commitments": [],
    "transactions": [
      { "hash": "4f2c…e7",
        "commitments": [ { "index": 40, "cm": "2a9f…07", "envelope": { "kem_ct": "…", "to_receiver": "…", "to_sender": "…", "body": "…" } } ],
        "nullifiers": ["8c04…d1", "5e77…20", "03aa…6f", "e19b…42"] } ] } ]

Каждая транзакция с bundle владеет здесь четырьмя нотами и четырьмя nullifier — своими четырьмя слотами, включая фиктивные, которые ни один читатель не отличит от настоящих (показана только первая строка commitment из примера). Начиная с v0.6.8 некоторые владеют и большим числом нот, после этих четырёх: invoke владеет одной нотой на каждую выплату, сначала pays, затем mints, каждая со своим конвертом выплаты, так что получатель находит её пробным расшифрованием; в цепочке с комиссиями моста, как chain 20, bridge_burn владеет своей нотой комиссии казначейства, а bridge_attest — своей нотой комиссии после её депозита. Конверт ноты комиссии пуст, он не запечатан ни для кого: кошелёк казначейства восстанавливает ноту из публичных полей транзакции.

Не более 128 блоков за вызов (более широкий диапазон обрезается) и, после первого блока, который всегда отдаётся целиком, 1,000 нот; продолжайте с последнего возвращённого height плюс один. Массив commitments на уровне блока содержит депозиты genesis на высоте 0 и пуст в остальных; производные ноты Withdraw и BridgeAttest появляются под их транзакцией. -32602 для обратного диапазона или пропущенной границы.

rand_getAnchor

Параметры [] для вершины или [height]. Результат { "height": 192, "root": "6b1d…c4" }, либо -32001 для высоты без записанного anchor. Anchor являются только корни на конец блока. Узел может ответить для старой высоты, но anchor bundle допустим лишь тогда, когда это один из последних proof_window_blocks корней: 1,024 на chain 20, 256 на chain до 19 включительно. Стройте относительно вершины.

rand_getWitness

Параметры [index]. Результат null за концом дерева, иначе путь Меркла этого листа, начиная с листа, ровно 32 уровня, с текущим корнем дерева:

{ "index": 40, "root": "6b1d…c4", "path": ["0000…00", "f2a1…3b", "…"] }

Самое дорогое чтение, которое выполняет узел (он перестраивает полное дерево глубины 32), и единственный запрос, раскрывающий что-то о вызывающем. Узел выполняет не более двух перестроек дерева одновременно, rand_getWitness и rand_getWitnesses вместе; запрос, ждущий слот дольше 10 s, отклоняется с -32000 witness builds are busy on this node; retry shortly.

rand_getWitnesses

Параметры [[index, …]], от 1 до 32 индексов листьев. Результат { "root": "…", "witnesses": [ { "index": 7, "path": ["…"] }, { "index": 9, "path": null } ] }: rand_getWitness, свёрнутый по многим листьям за одну сборку дерева, так что кошелёк, доказывающий несколько нот, платит за перестройку один раз. Индекс за концом дерева даёт для этой записи path: null. -32602 для пустого списка или более чем 32 индексов.

Ключи просмотра и раскрытие

rand_importViewingKey

Параметры [viewing_key] или [viewing_key, rescan_from_height], ключ в виде 64 шестнадцатеричных символов. Результат { "imported": true, "rescan_from_height": 0, "viewing_keys": 3 }. Узел хранит ключ только в памяти: не более 64 на узел, очищается при перезапуске; уже имеющийся ключ — холостая операция ("imported": false). Импорт никогда не сканирует; сканирование запускает следующий метод. -32602 для неверно сформированного ключа; -32000 на 65-м ключе.

Три метода ключей просмотра отвечают только вызывающему с loopback и отказывают всем остальным с -32000, если только узел не запущен с --rpc-viewing-open за чем-то, что выполняет аутентификацию. rand_removeViewingKey ([viewing_key] → { "removed": bool, "viewing_keys": n }) освобождает слот и обнуляет ключ. Реестр индексируется односторонним идентификатором ключа, blake3("rand-viewing-registry-id-1" ‖ nk), а не самим ключом, поэтому импорт — единственная долгоживущая копия, и удаление стирает её. Собственный JSON-текст запроса и временные копии, которые создаёт пробное расшифрование, не стираются.

rand_getViewingNotes

Параметры [viewing_key] или [viewing_key, from_index, limit] (limit по умолчанию и максимум 1,000). Каждый вызов продвигает сканирование ключа не более чем на 10,000 листьев, затем отдаёт страницу:

{ "scanned_index": 10041, "next_index": 10041, "complete": true,
  "notes": [
    { "index": 40, "cm": "2a9f…07", "height": 37, "role": "received",
      "note": { "pk": "…", "from": "…", "amount": "1500000000", "asset": 0, "time": 5, "memo": "coffee" },
      "nullifier": "8c04…d1", "spent": false },
    { "index": 43, "cm": "b310…88", "height": 39, "role": "sent",
      "note": { "pk": "…", "from": "…", "amount": "25000000", "asset": 0, "time": 9, "memo": null },
      "nullifier": null, "spent": null } ] }

role — received (конверт открыт как получателем, и нота называет pk ключа) или sent (открыт через исходящий ключ просмотра). memo ноты — зашифрованная заметка отправителя, текст UTF-8 не более 510 байт, либо null, если её нет или её поле открылось некорректно; некорректная заметка никогда не лишает получателя ноты. Заметка — это чей угодно текст: показывайте её как недоверенную. -32001 для ключа, который этот узел не импортировал.

rand_checkTransaction

Параметры [hash, key], key — TxKey отдельной транзакции в виде 64 шестнадцатеричных символов. Результат null для неизвестного хеша, иначе то, что раскрывает ключ:

{ "tx": "4f2c…e7", "height": 192,
  "disclosed": [ { "output": "bundle:0", "cm": "2a9f…07", "index": 40,
                   "note": { "pk": "…", "from": "…", "amount": "1500000000", "asset": 0, "time": 5, "memo": "invoice 7" } } ] }

Без состояния: ключ отбрасывается вместе с вызовом. output — это bundle:0 … bundle:3 для четырёх выходных слотов пакета (слоты 0–1 — выходы приватного актива, слоты 2–3 — выходы RAND, включая фиктивные), deposit — для конверта депозита в аттестации моста, token_mint — для ноты TokenMint, initial_mint — для начальной эмиссии RegisterToken, mint:0 — для единственного конверта эмиссии крана, либо (v0.6.8) payout:0 … payout:3 — для нот, выплаченных вызовом invoke, пронумерованных по их месту в переходе: сначала pays, затем mints; asset_bundle:* исчез вместе со вторым пакетом. В сети с комиссиями моста deposit раскрывает чистый депозит — брутто за вычетом комиссии. Раскрытая note содержит свой asset: этот вызов с ключом, которым отправитель запечатал ноту, — единственное место, где RPC раскрывает, какой актив был перемещён переводом. Она содержит и memo, как и rand_getViewingNotes: ключ, открывающий ноту, открывает и memo, поскольку они находятся в одном запечатанном теле. Неверный ключ возвращает { "disclosed": [] } — неотличимо от транзакции, которая ничего не раскрывает. Конверты вывода (withdraw) и генезисной аллокации не проверяются: они запечатываются внутри узла ключами, которые тут же отбрасываются, поэтому TxKey для них не существует. Конверт эмиссии крана проверяется: его получатель восстанавливает ключ через KEM-половину конверта (rand tx-key).

Транзакции

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": 1490435, "envelope_len": [1860, 1860, 1860, 1860],
                "auth_commit": "9217…ee9e", "auth_proof_bytes": 1361161 },
    "action": { "kind": "none" }
  }
}

bundle равен null у действий без пакета. Доказательства и конверты сообщаются только длиной. envelope_len равен 1,860 для каждого слота в сети chain 20, генезис которой фиксирует размер конверта ноты (envelope_bytes в rand_getLimits); в сетях chain 14–17 он составлял 1,348. auth_commit и auth_proof_bytes относятся к раздельной авторизации (начиная с chain 17): публичное обязательство c = H(AUTH, nk, salt), которое публикуют и доказательство пакета, и доказательство авторизации, и длина доказательства авторизации. В сети без hc_auth это нули и 0.

bundle — пакет со скрытым активом (начиная с chain 14): четыре входных и четыре выходных слота, поэтому всегда четыре nullifiers, четыре commitments и четыре envelope_len, включая фиктивные. Каждая транзакция несёт не более одного пакета; второго пакета нет. В нём нет поля asset: слоты 0–1 несут приватный актив, слоты 2–3 — RAND, и ничто публичное не говорит, какой актив переместили слоты 0–1. Перевод RAND и перевод любого токена RPL одинаково имеют "kind": "none" при burn_a, burn_r и burn_asset, равных 0, — одна и та же форма, поле в поле. Вида token_transfer нет. Три поля сжигания — единственное публичное заявление пакета о стоимости, покидающей пул:

  • burn_a / burn_asset — сожжённая сумма приватного актива и сам актив. Ненулевые при token_burn или bridge_burn, где равны amount и asset действия, а также при invoke, у которого inflow равен deposit или burn.
  • burn_r — сожжённый RAND. Ненулевое при bond (равно его amount), register_aggregator и invoke, вносящем RAND в хранилище программы.
  • fee — комиссия в RAND, всегда из слотов 2–3.

Представление действий (каждые amount, relayer_fee и initial_amount — десятичная строка; суммы токена выражены в его собственных единицах, а не в единицах RAND):

kindfields
none—
mintcm, amount, minter (base58-адрес валидатора)
deployprogram, words, public_words_len
callprogram, proof_len, input_envelope_len (null, если вызов его не несёт)
bondvalidator, amount, registered
unbondvalidator, amount, nonce
withdrawvalidator, amount, nonce, time
bridge_attestattestation_len, recipient, asset, asset_index, amount, time, r, commitment, pq_signers (индексы подписавших хранителей в pq_guardians; подписи по 2,420 байт не выводятся). Для ротации набора хранителей asset_index, amount и commitment равны null. r вычисляется как blake3("rand-deposit-r-1" ‖ mu) от дайджеста, подписанного хранителями. Начиная с v0.6.8 также deposit_amount (нота вкладчика: amount, то есть брутто, подписанное хранителями, за вычетом комиссии моста) и fee_note ({ amount, asset, time, r, commitment } — нота казначейства без владельца, либо null без комиссий моста); commitment — лист чистого депозита
bridge_burnasset, amount, relayer_fee, to_chain, token (обеспечение, которое погашается), to. Один пакет несёт всё сжигание: его burn_asset и burn_a равны asset и amount действия. Начиная с v0.6.8 также release_amount (сумма, которую освобождает подписанное тело, то есть amount за вычетом комиссии моста) и fee_note, как у attest
register_tokenname, symbol, decimals, authority ("none", "key", "bridge" или "program"), index, initial_amount, initial ({ amount, recipient, time, r } или null)
token_mintasset, amount, recipient, time, r, nonce
set_authorityasset, nonce, new_authority (base58, либо null при отказе от полномочий)
token_burnasset, amount; burn_asset / burn_a пакета повторяют их
pause_mintsnonce; без пакета и без комиссии, подписывается генезисным ключом паузы
unpause_mintsnonce, pq_signers; без пакета и без комиссии
register_bridged_tokenname, symbol, salt, chain, token, decimals (исходные decimals первого обеспечения), nonce, asset_id, pq_signers
list_backingtoken_index, chain, token, decimals, nonce, pq_signers
rotate_pq_guardiansnew_pq_guardians, nonce (израсходованный rotation_nonce), pq_signers (индексы в PQ-наборе до ротации); без пакета и без комиссии, только в сетях моста с rules_v2
rotate_pause_keynew_pause_key, nonce, pq_signers; то же
rotate_pq_guardians_v2, rotate_pause_key_v2v0.6.8: поля v1 плюс possession_signatures (количество) в PQ-форме. Без пакета и без комиссии; обязательны в сети, генезисное bridge.rotation которой задаёт needs_possession, как у chain 20. Ротация вступает в силу через delay_secs (86,400 с в chain 20)
cancel_rotationv0.6.8: rotation_kind ("pq_guardians" или "pause_key"), nonce; подписывается текущим ключом паузы, отменяет ожидающую ротацию
admit_validatorv0.6.8: candidate (адрес), candidate_key (hex), voters (адреса). Без пакета и без комиссии: голосование набора валидаторов за допуск к регистрации нового ключа в сети с staking.admission_by_vote, как у chain 20
slash_equivocationv0.6.8: offender, view, first и second ({ hash, height }); только в сетях с staking.slashing, к которым chain 20 не относится
invokev0.6.8, RPL-2: поля вызова (program, proof_len, input_envelope_len) и его transition (ниже). Только в сетях с секцией program_state, как у chain 20
register_aggregator, unbond_aggregator, withdraw_aggregator, slash_aggregator, aggregateих публичные поля; только в сетях с агрегацией
claim_vested, revoke_vesting, bond_vested, unbond_vestedentry (hex), amount (unvested при revoke), nonce; time при claim или revoke; validator и registers при bond; начиная с v0.6.8 signers при revoke (позиции его подписантов в revokers записи). Без пакета; только в сетях с генезисной секцией vesting, к которым chain 20 не относится

Регистрация токена и его эмиссии публичны, как и депозит через мост: каждое слово выпущенной ноты содержится в действии (её from — фиксированный MINT_FROM сети), поэтому получатель восстанавливает ноту по этим полям, ничего не расшифровывая. Строки видов bridge_attest, token_mint, register_token и invoke — это то, по чему сканирует кошелёк, и они закреплены тестом.

Вызов invoke на высоте 3,079 в chain 20, который прочитал одну ячейку, удалил её и выплатил две ноты из хранилища:

{ "kind": "invoke", "program": "aaaf…4b21", "proof_len": 1433874, "input_envelope_len": null,
  "transition": {
    "reads":  [{ "key": "0100…fa98", "value": "0000000004000000…00000000" }],
    "writes": [{ "key": "0100…fa98", "value": "0000000000000000000000000000000000000000000000000000000000000000" }],
    "inflow": "none",
    "pays":  [{ "asset": 0, "amount": "1000000000", "recipient": "rand1…", "time": 3027, "r": "87e6…9a67", "cm": "2555…6487" },
              { "asset": 4, "amount": "2000000000", "recipient": "rand1…", "time": 3027, "r": "b833…f390", "cm": "2b70…f204" }],
    "mints": [] } }

Всё это публично по замыслу. reads — ячейки, прочитанные программой, вместе с прочитанными значениями: доказательство построено относительно них, и реестр отклоняет транзакцию, если они перестали выполняться. writes — ячейки, которые она записала; записанное значение из 64 нулей удаляет ячейку. Ключ или значение ячейки — это Word8. Входящее принадлежит пакету: burn_r — RAND, внесённый в хранилище программы, а burn_a токена burn_asset — это токен, названный в inflow: deposit — в хранилище, burn — сжигание (собственный токен программы), none — когда burn_a равен 0. Исходящее — одна вычисленная сетью нота на каждую выплату: pays из хранилища, затем mints токена, полномочия эмиссии которого принадлежат этой программе. Каждое слово каждой ноты приведено здесь под именами полей token_mint; time берётся из пакета, cm — лист, добавленный сетью, а from ноты — фиксированный PROGRAM_FROM сети. Восемь выходных слов программы указаны в квитанции, как и у вызова.

Ни один ответ на перевод не содержит отправителя, получателя, nonce, суммы или актива: такого поля в сохранённом переводе нет.

rand_getRawTransaction

Параметры [hash]. Результат: вся зафиксированная транзакция в виде bincode hex, включая байты доказательства, — то, что нужно агрегатору и чего rand_getTransaction никогда не выводит.

Программы и квитанции

rand_getProgram

Параметры [program_id]. Результат — null или { "id", "base_pc", "words_len", "code_hash", "deployed_at", "public_words_len", "public_digest" }. Поля deployer нет: деплой оплачивается пакетом. public_words_len — длина публичного входа программы, заданного при деплое (0, если его нет); public_digest — его дайджест, значение, относительно которого проверяется доказательство каждого вызова, либо null, если входа нет.

rand_getProgramCode

Параметры [program_id]. Результат — null или { "base_pc": 0, "words": [ … ] }.

rand_getProgramPublic

Параметры: [program_id]. Результат: публичные слова программы, заданные при развёртывании, одной hex-строкой, каждое слово в виде 4 байт в порядке little-endian (8 hex-цифр на слово): [1, 2, 0xdeadbeef] — это "0100000002000000efbeadde". "" для программы, развёрнутой без публичного входа, null для идентификатора, которого нет ни у одной программы. Кошелёк, доказывающий вызов, передаёт эти слова доказывающему; реестр сверяет обязательство доказательства с public_digest программы.

rand_getProgramCell

v0.6.8, RPL-2. Параметры: [program_id, key], ключ — Word8 в виде 64 hex-символов (с 0x или без). Результат: { "key": "<64 hex>", "value": "<64 hex>" }. Ячейка, которую программа никогда не записывала или в которую записала нули (это удаляет её), читается как 64 нуля — единственное кодирование «отсутствует»; так же читается любой ключ программы, которую никто не развёртывал, поскольку существование программы не проверяется. { "enabled": false } в цепочке без раздела program_state. Ячейка публична по замыслу: именно её читает кошелёк, чтобы построить reads для invoke.

rand_getProgramCells

v0.6.8. Параметры: [program_id, { "after": "<64 hex>" | null, "limit": n }], объект необязателен. Результат: { "cells": [{ "key", "value" }, …], "next": "<64 hex>" | null }: ячейки программы в порядке ключей (беззнаковое сравнение восьми слов), начиная после after, не более limit штук (значение ограничивается диапазоном от 1 до 1 000). next — последний выданный ключ, если есть продолжение; передайте его обратно как after. На последней странице он равен null. Программа без ячеек отвечает { "cells": [], "next": null }; { "enabled": false } — без раздела.

rand_getProgramVault

v0.6.8. Параметры: [program_id]. Результат: [{ "asset": 0, "amount": "123" }, …], по возрастанию индекса актива (0 — это RAND, остальные индексы принадлежат реестру токенов), суммы — десятичные строки в собственных единицах актива. Строки с нулём не существует, поэтому пустое хранилище, как и программа, которую никто не развёртывал, — это []. { "enabled": false } — без раздела. Стоимость попадает в хранилище через пакет invoke (burn_r, а также burn_a актива burn_asset, когда inflow равен deposit) и покидает его в виде нот, которые называют pays перехода.

rand_getLimits

Параметры: []. Результат: всё, что кошельку нужно от генезиса цепочки для построения транзакции: пять лимитов вызова, размер конверта ноты, переключатель v0.6, auth-гость, условия газа и, начиная с v0.6.8, поля генезиса audit-v6 и лимиты состояния программ. В цепочке 20:

{ "max_program_words": 65535, "max_proof_bytes": 4194304, "max_block_bytes": 20971520,
  "max_call_envelope_bytes": 65536, "max_program_public_words": 32768,
  "envelope_bytes": 1860, "hardening_v6": true,
  "hc_auth": "1e4e347f44cf86750b30a9a4bdf9ec9256efe353d4ff8017451eca7d195639c1",
  "gas_metering": "circuit", "gas_price": "100", "byte_price": "800",
  "bundle_gas_limit": 20479, "adjust_bps": 1250,
  "max_gas_price": "10000", "max_byte_price": "80000", "byte_load": "paying",
  "admission_by_vote": true, "testnet": true, "slashing": null, "binding_domain": 1,
  "proof_window_blocks": 1024,
  "program_state": { "cell_fee": "10000000", "max_reads": 8, "max_writes": 8, "max_payouts": 4 } }

Генезис без пяти полей лимитов (цепочка 12) получает 4096, 2097152, 4194304, 18432 и 0. max_proof_bytes равнялся 8 388 608 в цепочках с 13 по 15.

fieldmeaning
envelope_bytes1860, когда генезис фиксирует для каждого конверта, создающего ноты, ровно такую длину — формат с memo; null в генезисе без этого поля (цепочки 14–17), где кошелёк запечатывает конверт в 1 348 байт без memo. Других значений не бывает
hardening_v6true, когда генезис применяет правила валидности v0.6. Тогда кошелёк доказывает вызов над публичным входом программы, за которым следует привязка вызова транзакции, а не над одним публичным входом: сначала выбираются ноты пакета комиссии, затем доказывается вызов, последним — пакет. Цепочка с этим флагом отвергает старое доказательство, цепочка без него — новое
hc_authauth-гость, который закрепляет генезис, в hex, или null. Если он задан, hc_bundle — это гость пакета v3, и каждый пакет несёт auth_commit и auth-доказательство: кошелёк передаёт гостю пакета nk и свежую соль и доказывает auth-гостя над ключом траты
gas_metering"circuit" в цепочке, генезис которой содержит раздел gas (счётчик внутри схемы); "header" в цепочке без него, когда узел применяет собственную политику газа (--gas-price, --byte-price); null при отсутствии и того и другого
gas_price, byte_priceединиц за газ и единиц за КиБ доказательства и конверта входа вызова, десятичными строками. При наличии раздела gas они являются состоянием консенсуса и одинаковы на каждом узле: с gas.dynamic, как в цепочке 20, это текущие цены на вершине, которые меняются от блока к блоку в зависимости от заполненности, никогда не опускаются ниже порогов генезиса (100 и 800) и никогда не поднимаются выше потолков; без него — фиксированные цены раздела. Без раздела это собственная политика данного узла, и два узла могут отвечать по-разному
bundle_gas_limitфиксированный газ, который должно объявлять доказательство каждого пакета, из раздела gas генезиса; null без него
adjust_bpsнаибольшее изменение цены за один блок у динамического регулятора, в базисных пунктах; null в цепочке без gas.dynamic
max_gas_price, max_byte_pricev0.6.8: потолки, выше которых регулятор цену не поднимает, десятичные строки; "10000" и "80000" в цепочке 20, null, где генезис их не задаёт (цепочки 18 и 19)
byte_loadv0.6.8: "paying", когда byte_price двигают только доказательство вызова и конверт входа, так что блок переводов не двигает ни одной цены, как в цепочке 20; null, где учитываются байты каждой транзакции (цепочки 18 и 19)
admission_by_votev0.6.8: нужно ли залогу, регистрирующему новый ключ валидатора, предварительное голосование набора валидаторов (rand_getAdmitted); true в цепочке 20
testnetv0.6.8: метка testnet в генезисе, позволяющая размещать faucet рядом с разделом моста; true в цепочке 20, false в каждой предыдущей
slashingv0.6.8: { "equivocation_bps", "jail_epochs" } — во что обходится эквивокация лидера, или null; в цепочке 20 — null
binding_domainv0.6.8: 0, где привязка транзакции и каждое подписанное сообщение действия привязываются только к идентификатору цепочки (цепочки 14–19), 1, где они несут ещё и хеш генезиса, так что доказательство или подпись, сделанные для одной цепочки, не проходят проверку ни в какой другой (цепочка 20). Кошелёк определяет, какую форму доказывать, по идентификатору цепочки, но не по этому ответу: кошелёк rand версии v0.6.8 применяет к идентификаторам цепочек 14–19 форму с идентификатором цепочки, а ко всем остальным — форму с привязкой к генезису, и читает это поле лишь для того, чтобы отказаться заранее. Кошелёк старше v0.6.8 не может отправлять транзакции в цепочке 20
proof_window_blocksv0.6.8: насколько старым в блоках может быть якорь пакета и насколько позади высоты может быть его time, одно окно для обоих; 1024 в цепочке 20, null (256) в цепочках до 19 включительно. Кошелёк с медленным или делегированным доказывающим читает его, чтобы узнать, сколько может занять доказательство
program_statev0.6.8, RPL-2: раздел program_state генезиса и лимиты его invoke, или null — тогда каждый invoke отвергается и ни один токен программы зарегистрировать нельзя. cell_fee — число единиц RAND (десятичная строка), на которое растёт нижняя граница комиссии invoke за каждую создаваемую им ячейку, то есть запись ненулевого значения в ячейку, читающуюся как нули; перезапись, удаление или запись нулей ничего не стоят. max_reads, max_writes и max_payouts (pays и mints вместе) — жёсткие пределы. На практике первым срабатывает правило сегмента: контекстные слова перехода (11 + 16 на каждое чтение или запись + 3 на каждую выплату) должны помещаться рядом с публичным входом программы и 8 словами привязки, не увеличивая публичную таблицу zkVM; для программы без публичного входа эта таблица занимает 128 строк, что оставляет 119 контекстных слов

Узел, не знающий какого-либо поля, отвечает без него. Ничто в этом ответе не аутентифицировано, поэтому кошелёк не принимает envelope_bytes на слово узла: кошелёк rand запечатывает прежний конверт для идентификаторов цепочек 14–17 независимо от ответа узла, и другим клиентам следует поступать так же.

rand_getReceipt

Параметры: [tx_hash]. Результат: null, пока вызов не зафиксирован, затем { "tx", "program", "tier": 14, "outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 17, "index": 0, "h_in": "9c0e…7f", "h_pub": null }. Поля effect нет. h_pub — дайджест публичного входа программы, заданного при развёртывании, относительно которого проверялось доказательство вызова, null для программы без него. У invoke (v0.6.8) квитанция такая же, как у вызова, и rand_getReceipts с темой receipts отдают её без изменений.

rand_getReceipts

Параметры: [program_id, from_height, to_height, limit?]. Результат: { "receipts": [ … ], "next_height": 2051 }: квитанции program_id при from_height <= height <= to_height, по высоте, затем по индексу, каждая — ровно тот объект, что возвращает rand_getReceipt. limit по умолчанию равен 256 и им же ограничен и никогда не разделяет высоту: когда страница уже содержит limit квитанций, она всё равно отдаёт остаток этой высоты. next_height — первая не отданная высота, или null, когда страницу закончил to_height. -32602 при to_height меньше from_height.

rand_getCallEnvelope

Параметры: [tx_hash]. Результат: null либо { "tx", "h_in", "kem_ct", "to_sender", "to_auditor", "body" } в hex — запечатанная запись входа вызова, отдаваемая дословно; у узла нет ключа, который её открывает. null, если вызов её не опубликовал (--no-envelope), транзакция не является вызовом или квитанции нет.

Стейкинг и предложение

rand_getValidators

Параметры: []. Результат: по одной строке на запись реестра, в порядке адресов, суммы — десятичные строки:

[{ "address": "…", "stake": "1000000000000",
   "pending": [{ "release_epoch": 41, "amount": "5000000000" }],
   "rewards": "4000000", "payout": "rand1…", "nonce": 3, "active": true, "jailed_until": null }]

jailed_until (v0.6.8) — первая эпоха, в которой ключ, подвергшийся слэшингу, снова может входить в набор (18446744073709551615 — навсегда), null, если ключ не заключён под стражу. В цепочке 20 нет staking.slashing, поэтому там он всегда null.

rand_getAdmitted

v0.6.8. Параметры []. Результат { "admission_by_vote": true, "max": 256, "admitted": ["<address>", …] }: ключи, за которые набор валидаторов проголосовал через AdmitValidator и которые ещё не зарегистрировались, в порядке адресов. В сети, чей genesis задаёт staking.admission_by_vote, как у chain 20, Bond, регистрирующий новый ключ, должен входить в их число. max — сколько ключей ledger хранит одновременно. В сети без этого флага admission_by_vote равен false, список всегда пуст, а для регистрации голосование не нужно. 2026-10-01 chain 20 ответила "admitted": [].

rand_getEpoch

Параметры []. Результат { "epoch": 41, "epoch_blocks": 1000, "next_set": ["…", "…"] }; next_set — это прогноз.

rand_getSupply

Параметры []. Результат:

{ "height": 1998,
  "genesis_deposited": "…", "genesis_staked": "…", "faucet_minted": "…",
  "faucet_epoch": "…", "faucet_minted_in_epoch": "…",
  "withdraw_deposited": "…", "fees_paid": "…", "burned": "…",
  "subsidised": "…", "sealed_blocks": "…", "aggregator_bonds": "…", "slashed": "…",
  "registration_fees_burned": "…",
  "vesting_issued": "…", "vesting_released": "…", "vesting_in_register": "…", "vesting_locked": "…",
  "program_rand_out": "…", "program_rand_held": "…",
  "pool_value": "…", "register_total": "…", "total_supply": "…", "invariant_holds": true }

subsidised, sealed_blocks, aggregator_bonds и slashed меняются только в сети с агрегацией. faucet_epoch и faucet_minted_in_epoch — это счётчик крана за эпоху в сопоставлении с staking.faucet_budget_per_epoch (состояние консенсуса в chain 20). Четыре поля vesting_* — счётчики genesis-вестинга; в сети без секции vesting, как chain 20, все они равны "0". program_rand_out и program_rand_held (v0.6.8, RPL-2; "0" без секции program_state) — это RAND, который вызовы invoke выплатили из хранилищ программ в виде нот (сторона пула, внутри pool_value), и то, что хранилища ещё удерживают (сторона реестра, внутри total_supply); он попал туда через burn_r вызова invoke, поэтому на стороне пула входит в burned. registration_fees_burned — это Σ сожжённых комиссий за регистрацию токенов по genesis-параметру tokens.burn_registration_fee, который chain 20 задаёт; invariant_holds вычитает её вместе с slashed из выпущенного сетью. Предложение объясняет это тождество.

rand_getEmission

Параметры []. Результат { "inflation": "0", "subsidy": null, "faucet": true }. inflation всегда равен "0". subsidy — расписание агрегации блоков (current, base, halving_blocks, sealed_blocks, next_halving_at); в сети без секции aggregation, как chain 20, оно равно null. faucet повторяет значение из rand_status.

Мост

Все четыре метода отвечают в любой сети; в сети без секции bridge состояние равно { "enabled": false }, а реестр — []. В genesis chain 20 она есть: набор хранителей 1 (guardian_set_index 1) с восемью хранителями, восемью PQ-хранителями, ключом паузы, burn_sequence 13 в genesis, нижней границей защиты от повторов (min_inbound_sequence 3 для исходных сетей 2, 3 и 4 и 7 для исходной сети 5), правилами моста rules_v2 с глобальным лимитом выпуска 4 000 zUSD за 24-часовое окно по всем обеспечениям и двумя группами, новыми в v0.6.8: rotation (ротация PQ-хранителей или ключа паузы требует доказательства владения новыми ключами и вступает в силу через 86 400 с после фиксации) и fees (10 bps с каждого депозита и 10 bps с каждого сжигания, остаются в Rand как нота zUSD на адрес казначейства). В chain 19 было burn_sequence 8, границы 1 и 4 и ни одной из этих групп. Её секция tokens содержит registration_fee в 1 RAND (сжигается), mint_cap_per_day для каждого обеспечения, равный 10000000000000 (100 000 zUSD при восьми десятичных знаках), и перечисляет сам zUSD: перенесён из chain 14 в genesis, тот же идентификатор актива, индекс 1, семь обеспечений.

rand_getBridgeState

Параметры []. Результат { "enabled": false } или:

{
  "enabled": true,
  "emitter": "01…",
  "emitters": { "2": "02…" },
  "guardian_set_index": 0,
  "guardians": ["aabb…"],
  "pq_guardians": ["…"],
  "mint_paused": false,
  "pause_nonce": 0,
  "list_nonce": 0,
  "pause_key": "…",
  "registration_fee": "1000000000",
  "burn_sequence": 1,
  "rotation_nonce": 0,
  "rules_v2": { "global_mint_cap_per_window": "…", "cap_window_secs": 86400,
                "global_minted_in_window": "…", "global_mint_headroom": "…" },
  "min_inbound_sequence": { "2": 2, "3": 2, "4": 2, "5": 2 },
  "rotation_rules": { "delay_secs": 86400, "needs_possession": true },
  "pending_rotations": [],
  "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" },
  "assets": [ … ]
}
полезначение
emitterадрес исходящего эмиттера этой сети, 32 байта в hex
emittersидентификатор исходной сети → адрес эмиттера, которому там доверяют
guardians20-байтовые адреса текущего набора, hex
pq_guardiansPQ-набор: открытые ключи Dilithium2 (1 312 байт), hex, с индексами, соответствующими текущему набору хранителей (набор 1 в chain 20). Каждый BridgeAttest несёт кворум соподписей от него. Ротация набора хранителей его не затрагивает; его меняет только rotate_pq_guardians, в сети с rules_v2
mint_pausedпока true, каждый transfer attest отклоняется с bridge minting is paused; сжигания и ротации остаются доступны
pause_nonceчто должна нести следующая пауза или снятие паузы; обе операции увеличивают его
list_nonceчто должен нести следующий register_bridged_token или list_backing; оба увеличивают его
pause_keyединственный ключ Dilithium2, который может приостановить выпуск, hex. Он может только приостанавливать: для снятия паузы нужен кворум PQ-хранителей
registration_feeчто RegisterBridgedToken должен сверх базовой стоимости пакета, в единицах RAND, десятичная строка
burn_sequenceсколько исходящих сообщений выпущено к этому моменту
rotation_nonceчто должна нести следующая rotate_pq_guardians / rotate_pause_key
rules_v2глобальный лимит выпуска и его скользящее окно или null в сети без правил моста v2. При них лимит на одно обеспечение — тоже скользящее окно, а листинг отклоняется, пока выпуск приостановлен. С v0.6.8 здесь также есть global_minted_in_window и global_mint_headroom, десятичные строки
min_inbound_sequenceнижняя граница защиты от повторов в genesis: идентификатор исходной сети → наименьший sequence, который может нести перевод из этой сети, обычные числа; null в сети без неё (chain 15 и ранее). bridge_attest ниже границы отклоняется с BelowReplayFloor, это окончательный вердикт. Релеер читает её, чтобы отбросить блокировку, которую сеть, от которой отделена эта, уже выпустила
rotation_rulesv0.6.8: { "delay_secs", "needs_possession" } из genesis-секции bridge.rotation или null. При них ротация — это действие _v2, несущее подписи владения новых ключей, и ждёт delay_secs, прежде чем вступить в силу
pending_rotationsv0.6.8: ротации, зафиксированные, но ещё не вступившие в силу, каждая — { "kind": "pq_guardians", "new_pq_guardians", "effective_at_secs" } или { "kind": "pause_key", "new_pause_key", "effective_at_secs" }; null без rotation_rules. cancel_rotation, подписанная текущим ключом паузы, отменяет одну из них
feesv0.6.8: { "mint_bps", "burn_bps", "recipient" } из genesis-секции bridge.fees или null (chain 14–19). Сеть оставляет эту долю с каждого депозита и каждого сжигания, округлённую вниз до целой единицы выпуска обеспечения, в виде ноты zUSD для recipient. Депозит зачисляет сумму брутто за вычетом комиссии; подписанное тело сжигания высвобождает его сумму за вычетом комиссии. Кошелёк читает её, чтобы оценить собственный депозит и, когда он сам recipient, чтобы восстановить свои ноты комиссий
assetsстроки rand_getAssets

Балансов нет: перенесённая стоимость — это ноты. Поля next_index больше нет: перенесённый токен получает листинг под индексом, который его регистрация уже зафиксировала, до того как его можно внести, поэтому предсказывать нечего. Индекс токена из листинга читайте из assets или rand_getTokens.

rand_getAssets

Параметры []. Результат: реестр активов моста по возрастанию индекса или []:

[{ "index": 1, "chain": 2, "token": "aaaa…", "asset_id": "…", "decimals": 6, "locked": "600",
   "mint_cap_per_day": "10000000000000", "minted_today": "1000", "mint_day": 20350,
   "minted_in_window": "1000", "mint_window_secs": 86400, "mint_headroom": "390000000000" }]

С v0.6.8 каждая строка обеспечения, здесь и везде, где обеспечения выдаются (rand_getBridgeState.assets, backings в rand_getToken, rand_getTokens и rand_getTokenSupply), несёт minted_in_window (десятичная строка) и mint_window_secs, оба равны null в сети со счётчиком по дням, а также mint_headroom: наибольший депозит в это обеспечение, который лимиты допускают ко времени блока в голове цепи, то есть лимит на обеспечение за вычетом уже выпущенного и, при rules v2, не больше, чем осталось в окне на весь реестр. Читайте mint_headroom, а не mint_cap_per_day − minted_today: эта разность игнорирует глобальный лимит. При rules v2, как в chain 20, minted_today — это счёт по скользящему окну, величина, с которой сверяется лимит на обеспечение; описание со счётчиком по дням ниже относится к chain 14.

Одна строка на обеспечение (исходную монету), поэтому токен с семью обеспечениями — это семь строк под одним index. index — слово asset, которое несёт нота этого актива; индекс 0 — это RAND, и его в реестре никогда нет. decimals — число десятичных знаков исходной монеты обеспечения. locked, mint_cap_per_day и minted_today — десятичные строки в собственных восьмизначных единицах токена, никогда не в единицах RAND; mint_day — обычное целое число, номер дня UTC. mint_cap_per_day — это genesis-параметр tokens.mint_cap_per_day, наибольшее количество, которое одно обеспечение может выпустить за день UTC по времени блока. minted_today — сколько обеспечение выпустило в mint_day, день UTC (timestamp_ms / 86,400,000) блока в голове цепи, поэтому счётчик, оставшийся с прежнего дня, читается как "0", как только голова цепи пересекает полночь. Депозит сверх лимита отклоняется с MintCapExceeded и становится допустимым на следующий день. chain и token — сетевая идентичность, о которой подписываются хранители; asset_id — blake3 от этих двух значений, то, что вычисляет rand_bridgeAssetId.

rand_bridgeAssetId

Параметры [token_chain, token_address] (целое число, 32 байта в hex). Результат: идентификатор актива, 64 hex-символа. Чистая арифметика.

rand_getBridgeBurn

Параметры [sequence]. Результат null или { "sequence": 0, "body_hex": "…", "digest": "…", "tx": "…", "height": 2, "amount": "…", "release_amount": "…", "fee": "…" }.

Последние три поля появились в v0.6.8, есть в каждой сети и заданы десятичными строками в восьмизначных единицах токена. release_amount — сумма, которую несёт подписанное тело, прочитанная прямо из body_hex: то, что высвобождает исходный контракт, из чего он оплачивает комиссию релеера из тела. fee — комиссия моста, которую сеть оставила как ноту казначейства, а amount — то, что сожгла связка, release_amount + fee. Без bridge.fees (chain 14–19) fee равна "0". Sequence 13 в chain 20, сжигание 0.999 zUSD: "amount": "99900000", "fee": "99900", "release_amount": "99800100".

Токены (RPL)

Реестр токенов RPL: все токены, перенесённые и нативные. Токены объясняют стандарт.

rand_getTokens

Параметры [from_index, limit], оба необязательные (0 и 1000; limit ограничивается 1 000). Результат: реестр по возрастанию индекса, начиная с from_index, читаемый диапазоном от этого индекса:

{ "enabled": true, "registration_fee": "1000000000", "next_index": 3, "max_tokens": 4096,
  "burn_registration_fee": true, "bound_note_value": true,
  "tokens": [
    { "index": 2, "id": "<64 hex>", "id_text": "rpl1…", "name": "Test Coin", "symbol": "TST",
      "decimals": 6,
      "authority": { "kind": "key", "key": "<Dilithium2 key, hex>", "address": "<base58>" },
      "mint_nonce": 1, "total_supply": "5700", "registered_at": 3 }
  ] }

На цепочке без раздела tokens: { "enabled": false, "tokens": [] }. index — слово asset, которое несёт нота токена (0 — это RAND, он никогда не перечисляется). id — идентификатор актива токена, id_text — его текстовая форма с контрольной суммой: bech32m, HRP rpl, поверх 32 байт идентификатора, 62 символа. authority принимает одно из значений:

kindостальные поля
none— (фиксированное предложение или отказ от полномочий)
keykey, address
bridgebackings: [{ "chain", "token", "decimals", "locked", "mint_cap_per_day", "minted_today", "mint_day" }], поля rand_getAssets, по одной строке на каждое обеспечение
programprogram

Предложение, locked, mint_cap_per_day, minted_today и registration_fee — десятичные строки (для registration_fee в единицах RAND, для остальных в единицах токена); next_index, mint_day и max_tokens — числа. max_tokens — предел реестра, заданный в genesis (4 096 на цепочке 20; null, если предела нет), сверх которого регистрация отклоняется с ошибкой RegistryFull. burn_registration_fee показывает, сжигается ли комиссия за регистрацию, а не выплачивается proposer’у; bound_note_value — является ли правилом валидности то, что mint или deposit на сумму от 2⁶³ и выше отклоняется (при допуске такая операция отклоняется на любой цепочке независимо от этого). Все три параметра заданы на цепочке 20. Страница короче limit — последняя.

Полномочие program ({ "kind": "program", "program": "<id>" }) можно зарегистрировать только на цепочке с разделом program_state, как цепочка 20 (v0.6.8): токен начинает с нулевым предложением, и выпускать или сжигать его могут только вызовы invoke этой программы.

Кошелёк определяет идентификатор токена через этот список, а не через rand_getToken: актив перевода на цепочке скрыт, а чтение всего реестра стоит одинаково, какой бы токен ни имелся в виду.

rand_getToken

Параметры [token]: индекс в реестре (число или десятичная строка), 64-символьный hex-идентификатор (с 0x или без, в любом регистре) или текстовая форма rpl1… (целиком в нижнем или целиком в верхнем регистре). Результат: строка этого токена из rand_getTokens или null. Некорректный идентификатор — с неверной контрольной суммой, другим HRP, неверной длиной, смешанным регистром или не в hex — даёт -32602, так что опечатка никогда не превратится в другой токен. Поиск по одному токену сообщает узлу, какой токен интересует вызывающего; он предназначен для обозревателей и разовых запросов.

rand_getTokenSupply

Параметры [token], в тех же формах. Результат null или:

{ "total_supply": "600",
  "backings": [{ "chain": 2, "token": "<32 bytes hex>", "decimals": 6, "locked": "600",
                 "mint_cap_per_day": "10000000000000", "minted_today": "1000", "mint_day": 20350 }] }

backings пуст для нативного токена; для токена с мостом суммы locked в сумме дают total_supply. Действует то же замечание о приватности, что и для rand_getToken.

Агрегация блоков (не активна на цепочке 20)

Эти методы есть в узле для цепочек, в genesis которых присутствует раздел aggregation. На цепочке 20 реестр пуст, и ничто никогда не распечатывается.

методпараметрырезультат
rand_getAggregate[hash]поля covers, aggregator, subsidy, proving_share, n, height запечатывающего агрегата; null для любой другой транзакции
rand_getAggregators[]реестр aggregator’ов: address, bond, payout, nonce, unbonding в каждой строке
rand_getUnsealed[from, limit]{ "bundles": [{ "hash", "height", "excess" }], "next_from" } — пакеты, которые aggregator ещё может покрыть

Aggregate отправляется через rand_sendTransaction, как и всё остальное.

Vesting в genesis (не активен на цепочке 20)

Эти методы есть в узле для цепочек, в genesis которых присутствует раздел vesting. Пока такого раздела нет ни у одной цепочки: на цепочке 20 каждый метод отвечает { "enabled": false }. Публичная конечная точка пересылает их с 2026-10-01.

методпараметрырезультат
rand_getVesting[id] или [id, at_ms], 64-символьный hex-идентификатор записизапись: её класс, получатель, график (amount, start_ms, cliff_ms, linear_ms, step_ms), что уже востребовано, отозвано и заложено, а также vested_now, claimable_now, unvested_now на момент as_of_ms (по умолчанию время головного блока); null для неизвестного идентификатора. Начиная с v0.6.8 отзываемая запись содержит revokers, threshold — сколько из них нужно для отзыва — и treasury, на который отзыв выплачивает средства (они заменяют единственный revoker), а в записи есть revoke_nonce и locked_now
rand_getVestingSummary[]{ "enabled": true, "height", "as_of_ms", "entries", "issued", "released", "locked", "classes": [ … ] }: итоги по классам, без ключей и владельцев
rand_getVestingSchedule[from_ms, to_ms, step_ms], не более 1 000 точек{ "enabled": true, "issued", "points": [{ "t_ms", "locked" }] }: общая заблокированная сумма реестра во времени

Суммы — десятичные строки. Четыре действия vesting отправляются через rand_sendTransaction.

Подписки (WebSocket)

ws://127.0.0.1:8545/ или /ws. Сокет обслуживает только rand_subscribe и rand_unsubscribe и на всё остальное отвечает -32601. Три темы — независимые потоки, порядок между которыми не гарантируется:

  • newHeads — в формате rand_getHead, отправляется один раз на каждый зафиксированный блок, по порядку, в том числе во время синхронизации. view здесь — собственный view блока.
  • receipts или receipts <program_id> — одно уведомление на каждый зафиксированный блок, содержащий хотя бы одну подходящую квитанцию вызова: { "height", "hash", "receipts": [ … ] }, каждая квитанция в формате rand_getReceipt.
  • transaction <hash> — одно уведомление, после чего узел удаляет подписку: { "status": "committed", "height", "index" } или { "status": "rejected", "reason" } при отказе, вызванном байтами самой транзакции (с тем же ограничением, что у rand_getTransactionStatus). Отказ, зависящий от состояния, не объявляется никогда, поэтому используйте таймаут.
{ "jsonrpc": "2.0", "id": 1, "method": "rand_subscribe",   "params": ["newHeads"] }
{ "jsonrpc": "2.0", "id": 2, "method": "rand_unsubscribe", "params": ["1"] }
{ "jsonrpc": "2.0", "id": 3, "method": "rand_subscribe",   "params": ["receipts", "<program_id>"] }
{ "jsonrpc": "2.0", "id": 4, "method": "rand_subscribe",   "params": ["transaction", "<hash>"] }

{ "jsonrpc": "2.0", "id": 1, "result": "1" }
{ "jsonrpc": "2.0", "id": 2, "result": true }
{ "jsonrpc": "2.0", "method": "rand_subscription",
  "params": { "subscription": "1", "result": { "height": 1998, "hash": "…", "view": 2251 } } }

Ограничения: 64 соединения на узел (65-е отклоняется при апгрейде с кодом 503), 8 подписок на соединение, 64 KiB на кадр клиента, ping каждые 30 с с ответом в течение 5 с и 256 записей backlog на поток — подписчик, отставший дальше на потоке, который он слушает, закрывается с кодом 1008 и не буферизуется; переподключитесь и восполните пропуск через rand_getCompactBlocks (heads), rand_getReceipts (квитанции) или rand_getTransactionStatus (транзакция). Простой GET / без заголовков апгрейда отвечает 400.

Ошибки

кодзначение
-32601неизвестный метод (включая выведенные из обращения методы счетов)
-32602неверный или отсутствующий параметр
-32000отклонено: транзакция, которую отказался принять мемпул, вызов faucet с превышением лимита частоты, построение witness, которое ждало дольше 10 с, чтение дольше 30 с, метод, который не обслуживает публичный слушатель, или подписка сверх лимита
-32001запрошенный объект не найден
-32010pruned: высота ниже порога хранения этого узла; data.floor указывает его (см. ниже)
-32603внутренняя ошибка
-32600неверный запрос: тело превышает допустимый размер, не JSON, некорректный пакет или уведомление
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "…" } }

Для rand_sendTransaction вердиктом о транзакции являются только -32000 и -32602; после любого другого кода её судьба неизвестна (см. rand_sendTransaction).

Pruned-узлы: ошибка -32010

Узел, запущенный с --prune-history <window>, хранит весь реестр (ноты, нуллификаторы, якоря, регистр), но только последние <window> блоков, транзакций и квитанций; rand_status.prune_floor — наименьшая высота, которую он ещё хранит. На цепочке 20 24 из 26 валидаторов работают с --prune-history 24h; два архивных узла запускаются без этого флага и хранят каждый блок, и публичная конечная точка пересылает запросы одному из них.

  • По высоте, ниже порога (кроме genesis): rand_getBlockByHeight, rand_getFinality по высоте, а также диапазон rand_getBlocks, rand_getCompactBlocks или rand_getReceipts, выходящий ниже порога, отвечают -32010 pruned: height h is below this node's retention floor f с data: { "floor": f }. Обратитесь к архивному узлу. [0, 0] по-прежнему отдаёт genesis.
  • По хэшу: rand_getBlockByHash, rand_getReceipt, rand_getRawTransaction, rand_getCallEnvelope и rand_getAggregate отвечают null для удалённого хэша, как и для никогда не существовавшего; различить их может только архивный узел. rand_getTransaction и rand_checkTransaction тоже отвечают null, а -32010 — только если уцелела строка расположения с высотой ниже порога. rand_getTransactionStatus на pruned-узле добавляет floor в каждую запись unknown.
  • Сканирование кошельком не затрагивается: rand_getCommitments, rand_getNullifiers, rand_getAnchor и методы witness читают семейства, которые никогда не удаляются.

Транзакция в сетевом формате

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>,
  auth_commit: Word8, auth_proof: Vec<u8>,      // since chain 17 (v0.6.3): split authorisation's c and
}                                               //   its proof; zeros and empty without hc_auth
Envelope { kem_ct: Vec<u8>, to_receiver: Vec<u8>, to_sender: Vec<u8>, body: Vec<u8> }

Action::None
Action::Mint { cm: Word8, pk: Word8, time: u32, r: Word8, envelope: Envelope, amount: u64,
               minter: PublicKey, signature: Signature }
Action::Deploy { base_pc: u32, words: Vec<u32>, public: Vec<u32> }
Action::Call { program: Hash, proof: Vec<u8>, input_envelope: Option<CallEnvelope> }
Action::Bond { validator: Address, amount: u64, registration: Option<Registration> }
Action::Unbond { validator: Address, amount: u64, nonce: u64, signature: Signature }
Action::Withdraw { validator: Address, amount: u64, nonce: u64, time: u32, r: Word8,
                   envelope: Envelope, signature: Signature }
Action::BridgeAttest { attestation: Vec<u8>, recipient: ShieldedAddress, r: Word8, time: u32,
                       asset: u32, envelope: Envelope, pq_signatures: Vec<PqSignature> }
Action::BridgeBurn { asset: u32, amount: u64, relayer_fee: u64, to_chain: u16,
                     token: [u8; 32], to: [u8; 32] }
// the five aggregation variants sit here in the enum's declared order
Action::RegisterToken { name: String, symbol: String, decimals: u8, authority: MintAuthority,
                        initial: Option<InitialMint>, salt: [u8; 32], index: u32 }
Action::TokenMint { asset: u32, amount: u64, recipient: ShieldedAddress, r: Word8, time: u32,
                    envelope: Envelope, nonce: u64, signature: Signature }
Action::SetAuthority { asset: u32, new: Option<PublicKey>, nonce: u64, signature: Signature }
Action::TokenBurn { asset: u32, amount: u64 }
Action::PauseMints { nonce: u64, signature: Signature }               // bundle-less, fee-less
Action::UnpauseMints { nonce: u64, pq_signatures: Vec<PqSignature> }  // bundle-less
Action::RegisterBridgedToken { name: String, symbol: String, salt: [u8; 32], chain: u16,
                               token: [u8; 32], decimals: u8, nonce: u64,
                               pq_signatures: Vec<PqSignature> }
Action::ListBacking { token_index: u32, chain: u16, token: [u8; 32], decimals: u8, nonce: u64,
                      pq_signatures: Vec<PqSignature> }
Action::RotatePqGuardians { new_pq_guardians: Vec<PublicKey>, nonce: u64,
                            pq_signatures: Vec<PqSignature> }         // bundle-less, fee-less
Action::RotatePauseKey { new_pause_key: PublicKey, nonce: u64,
                         pq_signatures: Vec<PqSignature> }            // bundle-less, fee-less
// the four vesting variants (ClaimVested, RevokeVesting, BondVested, UnbondVested) follow, tags 24 to 27;
// since v0.6.8 RevokeVesting carries `signatures: Vec<RevokerSignature>` in place of one `signature`
Action::AdmitValidator { candidate: PublicKey, signatures: Vec<(PublicKey, Signature)> }   // tag 28, v0.6.8
Action::SlashEquivocation { first: Box<SignedHeader>, second: Box<SignedHeader> }         // tag 29
Action::RotatePqGuardiansV2 { new_pq_guardians: Vec<PublicKey>, possession: Vec<Vec<u8>>,
                              nonce: u64, pq_signatures: Vec<PqSignature> }                // tag 30
Action::RotatePauseKeyV2 { new_pause_key: PublicKey, possession: Vec<u8>, nonce: u64,
                           pq_signatures: Vec<PqSignature> }                               // tag 31
Action::CancelRotation { kind: u8, nonce: u64, signature: Signature }                      // tag 32
Action::Invoke { program: Hash, proof: Vec<u8>, input_envelope: Option<CallEnvelope>,
                 transition: Transition }                                                  // tag 33, RPL-2

MintAuthority = None | Key(PublicKey) | Bridge { backings: Vec<Backing> } | Program(Hash)
InitialMint { amount: u64, recipient: ShieldedAddress, r: Word8, time: u32, envelope: Envelope }
Transition { reads: Vec<Cell>, writes: Vec<Cell>, inflow: Inflow, pays: Vec<Payout>, mints: Vec<Payout> }
Cell { key: Word8, value: Word8 }               // keys strictly ascending in each list
Inflow = None | Deposit | Burn                  // what the bundle's burn_a of burn_asset is to the program
Payout { asset: u32, amount: u64, recipient: ShieldedAddress, r: Word8, envelope: Envelope }
Registration { public_key: PublicKey, payout: ShieldedAddress, signature: Signature }
CallEnvelope { kem_ct: Vec<u8>, to_sender: Vec<u8>, to_auditor: Vec<u8>, body: Vec<u8> }
PqSignature { index: u8, signature: Vec<u8> }   // one guardian's Dilithium2 co-signature, by set index

Конфигурация bincode по умолчанию: целые числа фиксированной ширины, 8-байтовые префиксы длины, теги u32 для enum в порядке объявления. Пять вариантов агрегации идут после BridgeBurn и перед RegisterToken в enum (Транзакции); цепочка 20 никогда их не допускает, как и четыре варианта vesting и SlashEquivocation, но кодировщику, в котором номера тегов заданы жёстко, их всё равно нужно учитывать.

Два последних поля пакета появились в v0.6.3 и на цепочке 17. bincode позиционный, поэтому кодировщик, написанный для цепочки 16 или более ранней, не создаёт транзакцию, которую эта цепочка способна декодировать, и вместе с ними изменился каждый идентификатор транзакции: доменом идентификатора теперь служит rand-txid-3, который связывает auth_commit и дайджест auth proof. Клиент, который пересчитывает идентификаторы локально, должен перейти вместе с ней. На цепочке 20 каждый пакет несёт auth proof — как самостоятельно доказанный, так и делегированный. v0.6.8 ничего не меняет в раскладке Transaction и Bundle; он добавляет действия с 28 по 33.

Каждая транзакция несёт не более одного пакета (bundle). Bond несёт пакет, у которого burn_r равен его amount, а burn_a и burn_asset равны 0. BridgeBurn или TokenBurn сжигает токен через пакет: burn_asset должен совпадать с asset действия (и никогда не равняться 0), burn_a — с amount действия, а burn_r равен 0, при этом fee оплачивается в RAND из слотов 2–3. Все остальные действия не сжигают токенов, а RAND сжигается только через burn_r: пакет с burn_asset, равным 0, и ненулевым burn_a отклоняется везде. Значение r в BridgeAttest вычисляется, а не выбирается: для допуска требуется blake3("rand-deposit-r-1" ‖ mu) от дайджеста, подписанного хранителями (guardians), и любое другое значение отклоняется (WrongDepositBlinding, окончательный отказ). Мостовой токен должен быть внесён в список — при генезисе либо через RegisterBridgedToken / ListBacking — прежде чем какое-либо его подтверждение (attestation) может быть допущено; подтверждение монеты, не внесённой в список, отклоняется с ошибкой UnlistedToken.

Каждое доказательство пакета создаётся для той транзакции, в которой оно передаётся, и проверяется относительно неё: это вся транзакция с обнулёнными доказательствами, привязанная к идентификатору цепочки и, в цепочке 20 (binding_domain: 1), к хэшу генезис-блока, с новыми тегами. В цепочках с 14 по 19 привязка строилась только на идентификаторе цепочки, поэтому доказательство, созданное для цепочки 19, не проходит проверку в цепочке 20, а кошелёк старше v0.6.8 не может отправлять туда транзакции. В цепочках с 18 по 20 пакет без двух его доказательств кодируется в 7 936 байт, а транзакция перевода без них — в 7 949 (в цепочках с 14 по 16 — 5 848 и 5 861, с конвертами по 1 348 байт и без полей авторизации). Перевод в сети занимает около 2,85 МБ при производственном профиле, почти целиком это доказательства: доказательство пакета около 1,49 МБ и доказательство авторизации около 1,36 МБ. До разделённой авторизации он занимал около 1,43 МБ.

Последняя проверка по коммиту fullnode c0878c3f (chain 21) от 2026-10-12. Если код и эта страница расходятся, прав код; сообщите нам.