Rand Protocol
Documentation
Concepts fondamentaux

Transactions et actions

La transaction sur le réseau, son identifiant, chaque Action que connaît le registre, celles que chain 20 admet, et l'ordre dans lequel un nœud vérifie une soumission.

Utilisez cette page pour construire, soumettre ou décoder une transaction : la forme sur le réseau que prend rand_sendTransaction, le calcul de l’identifiant, la liste complète des actions avec leurs règles de bundle et de frais, et l’ordre d’admission qui détermine l’erreur que vous obtenez en premier.

La transaction

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 est un ensemble de huit mots u32 en little-endian, rendu en 64 caractères hexadécimaux minuscules. La forme sur le réseau est bincode(Transaction) avec les valeurs par défaut de bincode (entiers de largeur fixe, préfixes de longueur de 8 octets, étiquettes d’énumération u32), et rand_sendTransaction la prend en hexadécimal. Il n’y a aucune signature sur la transaction et aucun champ d’expéditeur : un bundle s’autorise lui-même par ses preuves (la preuve du bundle et, depuis la chaîne 17, la preuve d’authentification qui l’accompagne), et une action signée porte sa signature en elle-même. La protection contre le rejeu repose sur l’ensemble des nullifiers pour les bundles, sur un nonce par validator dans le registre pour les actions de staking signées, sur un mint_nonce par token pour TokenMint et SetAuthority, et sur le pause_nonce et le list_nonce du bridge pour ses actions de gouvernance.

Une transaction porte au plus un bundle, et chaque action qui n’est pas sans bundle en porte exactement un. Les emplacements 0–1 du bundle portent un actif privé et les emplacements 2–3 des RAND, de sorte qu’une seule preuve déplace un token et règle ses frais en RAND ; le burn du bridge à deux bundles de la conception antérieure a disparu : un burn est un seul bundle (Notes).

Le wallet rand construit tout cela par randprotocol_client::wallet::{send, submit} : sélection des entrées, récupération de l’ancre, calcul des témoins à partir de la copie de l’arbre propre au wallet, construction des sorties et scellement des quatre enveloppes, calcul du binding de la transaction, preuve du bundle et de la preuve d’authentification, vérification du condensat publié de la preuve du bundle, soumission.

Le binding de la transaction

Une transaction n’a pas de signature, et la preuve d’un bundle ne s’engage que sur le condensat propre au bundle : ancre, nullifiers, engagements, frais, champs de burn, time. Jusqu’à la chaîne 13, rien ne liait un bundle prouvé à l’action avec laquelle il circulait ni à ses enveloppes ; quiconque voyait une transaction pouvait donc conserver tous les octets de preuve et modifier le reste : la destination d’un BridgeBurn, le validator d’un Bond, une enveloppe. Depuis la chaîne 14, chaque preuve de bundle est produite sur le binding de la transaction et vérifiée par rapport à lui :

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

où ' signifie que chaque chaîne d’octets de preuve de bundle est remplacée par le vecteur vide : bundle.proof, depuis la chaîne 17 bundle.auth_proof, et le proof d’un Aggregate. Tout le reste est inclus : auth_commit, les quatre enveloppes, chaque champ de burn, une destination, un validator, un destinataire, les signatures, l’attestation du guardian et ses co-signatures Dilithium2, ainsi que la preuve propre à un Call. Les huit mots circulent dans le segment d’entrée publique de la preuve, dont le condensat H_PUB est déjà lié par le circuit ; le correctif n’a donc exigé aucune modification du guest ni du circuit ; le nœud exécute Machine::verify_public(hc_bundle, binding, proof) et refuse une preuve de bundle qui déclare toute autre hauteur publique (public height not the transaction binding's). Transaction::binding est l’unique fonction que le wallet appelle avant de prouver et que le ledger appelle avant de vérifier. Depuis la chaîne 17, la preuve d’authentification est produite sur les mêmes huit mots et vérifiée par rapport à eux, de sorte qu’elle ne peut pas non plus être déplacée vers une autre transaction.

La chaîne 20 lie le condensat de genèse. Jusqu’à la chaîne 19, le binding, ainsi que les messages signés des actions sans bundle, ne couvraient que l’identifiant de chaîne ; deux chaînes partageant un même identifiant de chaîne (une réédition, une copie privée) auraient donc accepté mutuellement leurs preuves et leurs signatures. La genèse de la chaîne 20 définit binding_domain: 1 (v0.6.8, audit v6 BIND-1 ; crates/randprotocol-core/src/types/binding.rs). Le condensat de genèse ouvre alors chaque préimage sous une étiquette d’une version supérieure : rand-tx-bind-2 et rand-call-bind-2 pour les deux bindings, et de même pour le mint du faucet (rand-mint-3), Unbond et Withdraw (rand-unbond-2, rand-withdraw-2), les messages de mint et d’autorité RPL (rand-rpl-mint-2, rand-rpl-authority-2), les actions d’aggregator et les messages de gouvernance du bridge. Un enregistrement de validator sur une telle chaîne est toujours le message v2 (genèse, chaîne, adresse, versement). Rien de ce qui a été prouvé ou signé pour une chaîne n’est valide sur une autre, quel que soit son identifiant de chaîne. Les mots du binding sont une entrée publique ; aucune clé de guest ni de vérifieur n’a donc changé. Un wallet antérieur à la v0.6.8 prouve encore selon la forme à identifiant de chaîne et est refusé sur la chaîne 20. rand_getLimits et rand_status fournissent binding_domain (1 sur la chaîne 20, 0 en l’absence du champ).

Sous le paramètre de genèse hardening_v6 (chaînes 16 à 20), la preuve propre à un Call est également liée, par un second condensat de même construction sous son propre domaine, la preuve de l’appel étant elle aussi vidée, en plus de celles du bundle :

call_binding(tx) = blake3("rand-call-bind-1" || bincode(chain_id, bundle', action''))   as 8 LE u32 words

Sur la chaîne 20, l’étiquette est rand-call-bind-2 et le condensat de genèse ouvre la préimage, comme ci-dessus. La preuve d’un Invoke est liée de la même manière, sa transition déclarée étant conservée dans la préimage, de sorte que la preuve s’engage exactement sur le changement d’état, les versements et les destinataires que porte la transaction.

La preuve de l’appel porte ces mots dans son segment d’entrée publique, après l’entrée publique du programme fixée au déploiement lorsqu’il en a une ; une copie de cette preuve rattachée à un autre bundle de frais ne se vérifie donc pas. Les deux bindings s’imbriquent : le wallet choisit les notes du bundle de frais et construit la transaction avec des preuves vides, prouve l’appel par rapport au binding de l’appel, y insère cette preuve, puis seulement ensuite calcule le binding de la transaction et prouve le bundle.

La conséquence pour un client : construisez la transaction entière — sorties, enveloppes, action — avant de prouver. Un wallet construit avant le fork prouve sur le segment public vide et est refusé. Les actions sans bundle n’ont aucune preuve à lier et reposent sur leurs propres signatures. Le binding n’est pas l’identifiant de la transaction, qui reste inchangé.

L’identifiant de la transaction

txid = blake3("rand-txid-3" || bincode(view))

où view est la transaction dont les octets proof du bundle sont remplacés par blake3(proof) (proof_hash) et ses octets auth_proof par blake3(auth_proof) (auth_proof_hash, après auth_commit, qui entre tel quel), tous les autres champs étant conservés tels quels. Le domaine était rand-txid-2, sans les deux champs d’authentification, jusqu’à la chaîne 16 ; tous les identifiants de transaction ont changé avec la chaîne 17. Hacher la preuve par son condensat est ce qui permet à un bundle élagué (agrégation de blocs, non active sur la chaîne 20) de conserver le même identifiant que sa forme brute ; un bundle élagué conserve sa preuve d’authentification. Une transaction sans bundle hache ses octets tels quels. Le même identifiant sert de clé au mempool, aux reçus et à toute consultation RPC.

Toutes les actions

Action est une énumération bincode, dont les étiquettes suivent l’ordre de déclaration :

tagactionchampslotplancher de frais
0None—requisBUNDLE_BASE = 0,001 RAND
1Mintcm, pk, time, r, envelope, amount, minter: PublicKey, signaturedoit être absent0 (signé par un validator ; chaînes de faucet uniquement, amount ≤ 100 RAND)
2Deploybase_pc: u32, words: Vec<u32>, public: Vec<u32>requisbase + 100 000 unités par mot de programme et par mot public
3Callprogram, proof, input_envelope: Option<CallEnvelope>requissur la chain 20, base + gas_price × le GAS_LIMIT déclaré de la preuve + byte_price par KiB de preuve et d’enveloppe d’entrée ; base + call_fee(tier) sur une chaîne sans section gas
4Bondvalidator, amount, registration: Option<Registration>requis, et burn_r == amountbase
5Unbondvalidator, amount, nonce, signaturedoit être absent0
6Withdrawvalidator, amount, nonce, time, r, envelope, signaturedoit être absent0 sur le réseau ; la base est prélevée sur amount
7BridgeAttestattestation, recipient: ShieldedAddress, r, time, asset, envelope, pq_signatures: Vec<PqSignature>requisbase
8BridgeBurnasset, amount, relayer_fee, to_chain: u16, token: [u8; 32], to: [u8; 32]requis, avec burn_asset == asset et burn_a == amountBRIDGE_BURN_FEE = 0,01 RAND
9RegisterAggregatorregistrationrequis, burn_r = la cautionbase
10UnbondAggregatoraggregator, nonce, signaturedoit être absent0
11WithdrawAggregatoraggregator, nonce, time, r, envelope, signaturedoit être absent0
12SlashAggregatordeux en-têtes d’agrégat signésdoit être absent0
13Aggregatecovers, proof, aggregator, nonce, time, r, envelope, signaturedoit être absent0
14RegisterTokenname, symbol, decimals: u8, authority: MintAuthority, initial: Option<InitialMint>, salt: [u8; 32], index: u32requisbase + le registration_fee du registre (1 RAND sur la chain 20, brûlé)
15TokenMintasset, amount, recipient: ShieldedAddress, r, time, envelope, nonce, signaturerequisbase
16SetAuthorityasset, new: Option<PublicKey>, nonce, signaturerequisbase
17TokenBurnasset, amountrequis, avec burn_asset == asset et burn_a == amountbase
18PauseMintsnonce, signaturedoit être absent0
19UnpauseMintsnonce, pq_signaturesdoit être absent0
20RegisterBridgedTokenname, symbol, salt, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signaturesrequisbase + registration_fee
21ListBackingtoken_index, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signaturesrequisbase
22RotatePqGuardiansnew_pq_guardians: Vec<PublicKey>, nonce, pq_signaturesdoit être absent0
23RotatePauseKeynew_pause_key: PublicKey, nonce, pq_signaturesdoit être absent0
24ClaimVestedentry: [u8; 32], amount, nonce, to: ShieldedAddress, time, r, envelope, signaturedoit être absent0 sur le réseau ; la base est prélevée sur amount
25RevokeVestingentry: [u8; 32], unvested, nonce, to: ShieldedAddress, time, r, envelope, signatures (depuis v0.6.8, un seuil des révocateurs de l’entrée, versant au trésor que nomme son entrée de genesis ; une seule signature auparavant)doit être absent0 sur le réseau ; la base est prélevée sur unvested
26BondVestedentry: [u8; 32], validator, amount, registration: Option<Registration>, nonce, signaturedoit être absent0
27UnbondVestedentry: [u8; 32], amount, nonce, signaturedoit être absent0
28AdmitValidatorcandidate: PublicKey, signatures: Vec<(PublicKey, Signature)>doit être absent0
29SlashEquivocationfirst, second : deux en-têtes de bloc signés d’un même leader pour une même vuedoit être absent0
30RotatePqGuardiansV2new_pq_guardians: Vec<PublicKey>, possession: Vec<Vec<u8>>, nonce, pq_signaturesdoit être absent0
31RotatePauseKeyV2new_pause_key: PublicKey, possession: Vec<u8>, nonce, pq_signaturesdoit être absent0
32CancelRotationkind: u8, nonce, signaturedoit être absent0
33Invokeprogram, proof, input_envelope: Option<CallEnvelope>, transition: Transitionrequis ; burn_r, et burn_a de burn_asset, sont ce qui entre dans le programmele plancher d’un call + cell_fee par cellule créée
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

Sur la chain 20, les tags 0 à 8, 14 à 21, 28 et 30 à 33 sont utilisables. Son genesis comporte une section bridge avec rules_v2, une section tokens, staking.admission_by_vote (tag 28), bridge.rotation (tags 30 à 32) et program_state (tag 33). Les tags 22 et 23 y sont refusés : avec bridge.rotation.needs_possession, une rotation doit prendre la forme …V2, qui porte la signature propre à chaque nouvelle clé. Sur la chain 19, qui n’avait aucune de ces sections, les tags 0 à 8 et 14 à 23 étaient utilisables (la chain 14, sans rules_v2, refusait 22 et 23). SlashEquivocation exige une section staking.slashing, et celle de la chain 20 est nulle. Les cinq actions d’agrégation exigent une section aggregation, que la chain 20 n’a pas (un build refuse de démarrer sur un genesis qui en contient une tant que l’agrégation n’a pas été re-mesurée). Un nœud refuse toutes ces actions à l’entrée, avant tout autre contrôle, mais elles occupent tout de même leurs tags bincode : un encodeur qui code en dur les numéros de tag doit donc les compter. L’agrégation est documentée comme à venir dans Proofs. Les quatre actions de vesting du genesis (tags 24 à 27, dans le build depuis v0.5.11) exigent une section vesting, qu’aucune chain n’a eue, chain 20 comprise, et sont refusées à l’admission sans elle. Les tags 28 à 33 sont nouveaux dans v0.6.8 et ajoutés en dernier, de sorte que l’historique de toutes les chaînes antérieures se décode toujours.

Les quatre actions RPL (tags 14 à 17) enregistrent un token, en mintent davantage pour un token autorisé par Key, transmettent ou renoncent à son autorité de mint, et brûlent une position ; un RegisterToken qui nomme une autorité Bridge ou Program est refusé. Il n’existe pas d’action de transfert de token : un transfert de token est un None, indiscernable d’un paiement en RAND. Voir RPL tokens. Les quatre actions de gouvernance du bridge (tags 18 à 21) sont la pause des mints, sa levée, et l’inscription d’un token bridgé ou d’un collatéral supplémentaire après le genesis. PauseMints est signé par la clé de pause du genesis, qui ne peut que mettre en pause ; les trois autres exigent un quorum des gardiens Dilithium2 (PQ). Un BridgeAttest porte lui aussi un tel quorum, à côté de son quorum ECDSA. Les tags 22 et 23, sous rules_v2 du bridge, remplacent l’ensemble des gardiens PQ ou la clé de pause, chacun sous un quorum PQ de l’ensemble actuel et le rotation_nonce du bridge. Les tags 30 et 31 font de même sous bridge.rotation et portent en outre la signature Dilithium2 propre à chaque nouvelle clé sur le message de rotation, preuve que la clé est bien détenue ; sur la chain 20, une rotation est enregistrée comme en attente et prend effet après delay_secs, soit 86 400 s. CancelRotation (tag 32), signé par la clé de pause actuelle, abandonne une rotation en attente avant qu’elle ne prenne effet. Voir Bridge.

AdmitValidator (tag 28) est le vote de l’ensemble des validators pour admettre une nouvelle clé : les signatures de validators détenant plus des deux tiers du stake, dans l’ordre croissant des adresses. Avec staking.admission_by_vote (chain 20), un Bond qui enregistrerait un nouveau validator est refusé si son adresse n’a pas été admise de cette façon (Staking). Invoke (tag 33, RPL-2) est un call dont la preuve atteste une transition d’état déclarée du programme : les cellules lues et écrites, ce que les champs de burn du lot apportent au coffre du programme, et les notes que le coffre verse ou que le programme mint. Le registre applique la transition si chaque lecture déclarée est toujours valable et si les deux preuves sont vérifiées (Programs).

Un Deploy et un Call sont des transactions shielded ordinaires dont le lot est un auto-transfert de zéro : il sert à payer le plancher avec les notes de l’appelant, de sorte que la chaîne n’apprend jamais à qui elles appartenaient. RAND ne quitte le pool que par burn_r — le stake d’un Bond, la caution d’un RegisterAggregator, le dépôt d’un Invoke dans un coffre de programme — et un token seulement par burn_a, avec burn_asset qui le désigne, sur un TokenBurn, un BridgeBurn ou un Invoke. Toute autre action ne brûle rien, et un lot avec burn_asset = 0 et burn_a non nul est refusé partout.

Ordre d’admission

Ledger::validate_inner vérifie les points peu coûteux avant les coûteux, dans cet ordre ; le mempool exécute la même vérification avant de diffuser, de sorte qu’une transaction invalide est refusée une seule fois, à la périphérie.

  1. Plafonds de taille : la transaction entière ≤ max_block_bytes de la chaîne ; chacune des quatre enveloppes du bundle, et toute enveloppe de note que l’action porte, exactement le envelope_bytes de la genèse (1 860 sur la chaîne 20 ; au plus 2 048 octets sur une chaîne sans ce champ) ; la preuve du bundle, la preuve d’autorisation et une preuve d’appel chacune ≤ max_proof_bytes (2 MiB par défaut, 4 MiB dans la genèse de la chaîne 20 ; c’était 8 MiB sur les chaînes 13 à 16) ; un programme ≤ le plafond de programme de la chaîne (4 096 mots par défaut, 65 535 dans la genèse de la chaîne 20) ; une enveloppe d’appel ≤ max_call_envelope_bytes (18 432 octets par défaut, 65 536 sur la chaîne 20) ; une attestation ≤ 16 KiB. Une preuve de bundle sous la forme de marqueur scellée (élaguée) est refusée ici hors synchronisation en forme scellée (PrunedFormOutsideSync), avant tout travail sur la preuve.
  2. Identifiant de chaîne : il correspond.
  3. Forme et plancher de frais : une action sans bundle ne porte aucun bundle, et toutes les autres en portent exactement un ; check_burn_shape fixe lequel des trois champs de destruction l’action peut renseigner : un TokenBurn ou un BridgeBurn détruit son propre asset et son amount via burn_asset et burn_a, un Bond ou un RegisterAggregator détruit son montant via burn_r, et toute autre action ne détruit rien ; fee ≥ fee_floor(action). Sur une chaîne dotée d’une section gas (chaîne 20), le plancher d’un Call ou d’un Invoke à cette étape est la règle du gas à une limite déclarée de 1, BUNDLE_BASE + gas_price + byte_price × ⌈bytes / 1024⌉ sur sa preuve et son enveloppe d’entrée, qui sont connues avant toute vérification.
  4. Ancre : l’une des 1 024 dernières racines de fin de bloc sur la chaîne 20, dont la genèse fixe proof_window_blocks: 1024 (v0.6.8) ; l’une des 256 dernières sur une chaîne sans ce champ, comme jusqu’à la chaîne 19.
  5. Temps : dans [height − 1024, height] sur la chaîne 20 ; [height − 256, height] sans le champ. La valeur unique de la genèse fixe les deux fenêtres.
  6. Nullifiers et engagements : les quatre nullifiers sont distincts et aucun n’est dépensé ; les quatre engagements sont distincts et aucun n’est déjà une feuille.
  7. Contrôles propres à l’action : le faucet est activé, le mint est sous le plafond et signé par un validateur, et sur la chaîne 20 il désigne un destinataire figurant sur la liste d’autorisation staking.faucet_recipients de la genèse, tient dans le faucet_budget_per_epoch de l’époque et est signé par un validateur de la liste staking.faucet_minters de la genèse (une règle de validité depuis la chaîne 16) ; un mint ou un dépôt égal ou supérieur à 2⁶³ est refusé sur toutes les chaînes ; le programme se décode ; le programme existe ; les règles de staking (enregistrement présent exactement quand le validateur est inconnu, le nonce, la signature, une mise suffisante) ; les règles propres au bridge pour BridgeAttest et BridgeBurn et pour les actions de pause et de listage ; les règles de RPL pour ses quatre actions, y compris les frais d’enregistrement ; sur la chaîne 20, le quorum du vote d’admission, et pour un Invoke la forme de la transition, les lectures déclarées face aux cellules stockées, la couverture du coffre pour chaque paiement et les frais de cellule. Tout cela s’exécute avant la preuve du bundle, de sorte qu’une transaction qui ne peut pas s’appliquer ne coûte aucune vérification STARK. Sous hardening_v6 (chaînes 16 à 20), deux refus supplémentaires figurent ici : un Deploy d’un programme qu’aucun appel ne pourrait détenir (ProgramUncallable), et une preuve de bundle ou d’appel dont l’en-tête ou la transcription porte un champ que le prouveur honnête n’écrirait pas (non-canonical proof: …), refusée avant que l’une ou l’autre preuve ne soit vérifiée. Depuis v0.6.8, une preuve dont l’en-tête déclare un degré supérieur au plafond de la table la plus haute est refusée en premier sous le même verdict (audit v6 ZKV-4), avant que le contrôle ne la parcoure.
  8. Condensat du bundle : sur une chaîne à autorisation scindée (toute chaîne depuis la 17), les champs d’autorisation sont contrôlés en premier : un bundle sans preuve d’autorisation est refusé, le c que publie la preuve d’autorisation (lu, pas encore vérifié) doit être égal à auth_commit, et sur la chaîne 20 la preuve d’autorisation doit déclarer un GAS_LIMIT d’exactement 1 279. Ensuite, le registre recalcule le condensat des champs publiés du bundle, auth_commit compris, et il doit être égal à ce que la preuve a publié. Sur la chaîne 20, la preuve du bundle doit aussi déclarer un GAS_LIMIT d’exactement le bundle_gas_limit de la genèse, 20 479.
  9. Preuve du bundle : Machine::verify_public contre le hc_bundle ancré dans la genèse et la liaison de la transaction. Sur une chaîne à autorisation scindée, la preuve d’autorisation est vérifiée ensuite, contre le hc_auth ancré dans la genèse et la même liaison.
  10. Preuve d’appel et frais de palier : pour un Call ou un Invoke, l’en-tête de la preuve est d’abord fixé (palier au plus 14, une table keccak au plus 2¹², une table sha256 au plus 2¹³, la hauteur du programme déployé, plancher de 2⁷ lignes sous hardening_v6, et sur la chaîne 20 un GAS_LIMIT déclaré au plus égal au plafond gas_max de l’en-tête), puis le STARK de l’appel contre le code_hash du programme et, sous hardening_v6, la liaison d’appel de la transaction. Les frais sont contrôlés en dernier, car on ne peut les connaître qu’une fois la preuve décodée : sur la chaîne 20, fee ≥ BUNDLE_BASE + gas_price × GAS_LIMIT + byte_price × ⌈bytes / 1024⌉ aux prix en vigueur, plus cell_fee par cellule créée pour un Invoke (Frais) ; sur une chaîne sans section gas, fee ≥ BUNDLE_BASE + call_fee(tier).

Les erreurs reviennent de rand_sendTransaction avec le code -32000 et le motif : nullifier already spent, anchor is not one of the last 1024 roots (256 sur une chaîne sans 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, et depuis la chaîne 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, depuis la chaîne 17 this chain requires an auth proof on every bundle (genesis hc_auth) et the auth proof publishes a different commitment than the bundle's auth_commit, et sur la chaîne 20 envelope is 1348 bytes, this chain requires exactly 1860 et bundle proof declares gas limit …, this chain requires exactly 20479. L’acceptation n’est pas l’inscription : interrogez rand_getTransaction jusqu’à ce qu’elle renvoie un bloc.

Un bloc contenant une transaction invalide est invalide. Le mempool indexe chaque nullifier et chaque engagement en attente et refuse tout conflit d’emblée, de sorte que deux bundles dépensant la même note ne partagent jamais un bloc.

Tailles

élémentoctets
Word832
une Envelope1 892 encodés (1 860 de texte chiffré) depuis la chaîne 18 ; 1 380 (1 348) jusqu’à la chaîne 17
Bundle moins ses deux preuves7 936 (quatre nullifiers, quatre engagements, quatre enveloppes, auth_commit)
transaction de transfert moins ses deux preuves7 949
transaction de déploiement moins ses deux preuves, programme de 100 mots, sans entrée publique8 369
transaction d’appel moins ses trois preuves, sans enveloppe d’entrée7 990
transaction de mint (sans bundle)5 761 (une clé Dilithium2 de 1 312 octets et une signature de 2 420 octets)
une preuve de bundle, palier 14, profil de production1 497 544 et 1 504 260 lus sur la chaîne 18 (un déploiement à la hauteur 97 821 et un appel à 98 335, 2026-09-30) ; 1 422 116 et 1 423 170 mesurés sur la chaîne 14 (l’enregistrement de zUSD et un listage de backing, 2026-09-20)
une preuve d’autorisation, palier 10, profil de production1 370 827 et 1 367 303 dans les deux mêmes transactions de la chaîne 18

Les six premières lignes sont l’encodage depuis la chaîne 18 : le champ mémo a ajouté 512 octets à chaque enveloppe, et l’autorisation scindée a ajouté 40 octets au bundle avant les octets propres de la preuve d’autorisation. Un transfert shielded sur le réseau pèse donc environ 2,88 MB depuis la chaîne 18, chaîne 20 comprise, presque entièrement de la preuve, quel que soit l’actif qu’il déplace : environ 1,5 MB de preuve de bundle et 1,37 MB de preuve d’autorisation. Il pesait environ 1,43 MB jusqu’à la chaîne 15, avec une seule preuve. Les blocs sont plafonnés à 2 000 transactions (MAX_BLOCK_TXS) et au max_block_bytes de la chaîne en octets de transaction, deux règles de consensus : 4 MiB par défaut (MAX_BLOCK_BYTES), ce qui contiendrait un seul tel transfert, et 20 MiB dans la genèse de la chaîne 20, qui en accueille sept. Une genèse avec hc_auth doit fixer max_block_bytes à au moins trois fois max_proof_bytes plus 1 MiB, puisqu’une transaction peut porter trois preuves (bundle, autorisation, appel) ; la valeur par défaut de 4 MiB y est donc refusée. L’agrégation au niveau du bloc est le remède prévu ; voir Preuves.

Relire une transaction

rand_getTransaction(hash) renvoie null tant que la transaction n’est pas inscrite, puis sa position dans le bloc et uniquement les champs publics :

{
  "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" }
  }
}

Un transfert ne comporte ni from, ni to, ni nonce, ni montant, ni champ asset, et le bloc stocké n’en contient pas davantage : le nœud n’a plus rien à expurger. auth_commit et auth_proof_bytes (la longueur de la preuve d’autorisation) y figurent depuis la chain 17. Le bundle comporte toujours quatre emplacements, leurres compris, et un transfert de RAND comme un transfert de n’importe quel token RPL sont tous deux de type "kind": "none". Chaque montant u64 est une chaîne décimale depuis la chain 14 (fee, burn_a, burn_r, le amount d’une action) ; les indices, nonces, hauteurs et longueurs restent des nombres JSON. Les autres types d’action affichent leurs champs publics — 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, les quatre types de vesting claim_vested, revoke_vesting, bond_vested, unbond_vested, et, depuis la v0.6.8, admit_validator, slash_equivocation, rotate_pq_guardians_v2, rotate_pause_key_v2, cancel_rotation et invoke — comme indiqué dans la référence JSON-RPC.

Dernière vérification par rapport au commit c0878c3f de fullnode (chaîne 21) le 2026-10-12. Lorsque le code et cette page divergent, c'est le code qui a raison ; signalez-le-nous.