Usa questa pagina per costruire, inviare o decodificare una transazione: la forma sul wire che rand_sendTransaction accetta, come si calcola l’id, l’elenco completo delle azioni con le relative regole di bundle e di commissione, e l’ordine di ammissione che decide quale errore si riceve per primo.
La transazione
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 è composto da otto parole u32 little-endian, rappresentate come 64 caratteri esadecimali minuscoli. La forma sul wire è bincode(Transaction) con i valori predefiniti di bincode (interi a larghezza fissa, prefissi di lunghezza di 8 byte, tag di enum u32), e rand_sendTransaction la accetta in formato esadecimale. Non esiste alcuna firma sulla transazione né un campo mittente: un bundle si autorizza da sé tramite le proprie prove (la prova del bundle e, dalla chain 17, la prova di autorizzazione che la affianca), mentre un’azione firmata porta la firma al proprio interno. La protezione dal replay è l’insieme dei nullifier per i bundle, un nonce per validator nel registro per le azioni di staking firmate, un mint_nonce per token per TokenMint e SetAuthority, e i pause_nonce e list_nonce del bridge per le sue azioni di governance.
Una transazione contiene al massimo un bundle, e ogni azione che non sia priva di bundle ne contiene esattamente uno. Gli slot 0–1 del bundle contengono un asset privato e gli slot 2–3 RAND, così una sola prova sposta un token e ne paga la commissione in RAND; il burn del bridge a due bundle del progetto precedente non esiste più: un burn è un solo bundle (Notes).
Il wallet rand costruisce tutto questo tramite randprotocol_client::wallet::{send, submit}: seleziona gli input, recupera l’ancora, calcola i testimoni a partire dalla copia dell’albero posseduta dal wallet, costruisce gli output e sigilla tutte e quattro le buste, calcola il binding della transazione, genera la prova del bundle e la prova di autorizzazione, verifica il digest pubblicato della prova del bundle e invia.
Il binding della transazione
Una transazione non ha firma, e la prova di un bundle si impegna solo sul digest del bundle stesso: ancora, nullifier, commitment, commissione, campi del burn, time. Fino alla chain 13 nulla legava un bundle dimostrato all’azione con cui viaggiava o alle sue buste, per cui chiunque vedesse una transazione poteva conservare ogni byte della prova e cambiare il resto: la destinazione di un BridgeBurn, il validator di un Bond, una busta. Dalla chain 14 ogni prova di bundle è generata su, e verificata rispetto a, il binding della transazione:
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
dove ' indica che ogni stringa di byte di prova del bundle è sostituita dal vettore vuoto: bundle.proof, dalla chain 17 bundle.auth_proof, e la proof di un Aggregate. Tutto il resto è incluso: auth_commit, tutte e quattro le buste, ogni campo del burn, una destinazione, un validator, un destinatario, le firme, l’attestazione del guardiano e le sue co-firme Dilithium2, e la prova propria di una Call. Le otto parole viaggiano nel segmento di input pubblico della prova, il cui digest H_PUB il circuito già vincola, quindi la correzione non ha richiesto modifiche al guest né al circuito; il nodo esegue Machine::verify_public(hc_bundle, binding, proof) e rifiuta una prova di bundle che dichiari un’altra altezza pubblica (public height not the transaction binding's). Transaction::binding è l’unica funzione che il wallet chiama prima di dimostrare e il ledger prima di verificare. Dalla chain 17 la prova di autorizzazione è generata su, e verificata rispetto a, le stesse otto parole, quindi non può essere spostata su un’altra transazione.
La chain 20 vincola l’hash di genesi. Fino alla chain 19 il binding, e i messaggi firmati delle azioni prive di bundle, coprivano soltanto il chain id, per cui due chain con lo stesso chain id (una ripubblicazione, una copia privata) si sarebbero accettate a vicenda prove e firme. Il genesis della chain 20 imposta binding_domain: 1 (v0.6.8, audit v6 BIND-1; crates/randprotocol-core/src/types/binding.rs). L’hash di genesi apre quindi ogni preimmagine sotto un tag di una versione successiva: rand-tx-bind-2 e rand-call-bind-2 per i due binding, e lo stesso vale per il mint del faucet (rand-mint-3), Unbond e Withdraw (rand-unbond-2, rand-withdraw-2), i messaggi di mint e di autorità RPL (rand-rpl-mint-2, rand-rpl-authority-2), le azioni degli aggregator e i messaggi di governance del bridge. Su una chain di questo tipo la registrazione di un validator è sempre il messaggio v2 (genesi, chain, indirizzo, payout). Nulla di ciò che è stato dimostrato o firmato per una chain risulta valido su un’altra, qualunque sia il suo chain id. Le parole del binding sono un input pubblico, quindi nessuna chiave del guest o del verifier è cambiata. Un wallet precedente alla v0.6.8 dimostra ancora rispetto alla forma con il chain id e viene rifiutato sulla chain 20. rand_getLimits e rand_status restituiscono binding_domain (1 sulla chain 20, 0 se il campo manca).
Con il genesis hardening_v6 (chain da 16 a 20) anche la prova propria di una Call è vincolata, tramite un secondo digest della stessa costruzione sotto un proprio dominio, con la prova della chiamata azzerata oltre a quelle del bundle:
call_binding(tx) = blake3("rand-call-bind-1" || bincode(chain_id, bundle', action'')) as 8 LE u32 words
Sulla chain 20 il tag è rand-call-bind-2 e l’hash di genesi apre la preimmagine, come sopra. La prova di un Invoke è vincolata allo stesso modo, con la transizione dichiarata mantenuta nella preimmagine, così che la prova si impegni esattamente sul cambio di stato, sui pagamenti e sui destinatari che la transazione contiene.
La prova della chiamata porta queste parole nel proprio segmento di input pubblico, dopo l’input pubblico del programma fissato al deploy quando ne ha uno, per cui una sua copia allegata a un altro bundle di commissione non viene verificata. I due binding sono annidati: il wallet sceglie le note del bundle di commissione e costruisce la transazione con le prove vuote, dimostra la chiamata rispetto al binding della chiamata, inserisce quella prova e solo allora calcola il binding della transazione e dimostra il bundle.
La conseguenza per un client: costruire l’intera transazione — output, buste, azione — prima di dimostrare. Un wallet costruito prima del fork dimostra rispetto al segmento pubblico vuoto e viene rifiutato. Le azioni prive di bundle non hanno prove da vincolare e si basano sulle proprie firme. Il binding non è l’id della transazione, che resta invariato.
L’id della transazione
txid = blake3("rand-txid-3" || bincode(view))
dove view è la transazione con i byte proof del bundle sostituiti da blake3(proof) (proof_hash) e i suoi byte auth_proof da blake3(auth_proof) (auth_proof_hash, dopo auth_commit, che entra così com’è), con ogni altro campo invariato. Fino alla chain 16 il dominio era rand-txid-2, senza i due campi di autorizzazione; con la chain 17 ogni id di transazione è cambiato. Calcolare l’hash della prova tramite il suo digest è ciò che permette a un bundle potato (aggregazione dei blocchi, non attiva sulla chain 20) di mantenere lo stesso id della sua forma grezza; un bundle potato conserva la propria prova di autorizzazione. Una transazione priva di bundle calcola l’hash dei propri byte così come sono. Lo stesso id fa da chiave per la mempool, le ricevute e ogni ricerca via RPC.
Tutte le azioni
Action è un enum bincode, con tag in ordine di dichiarazione:
| tag | azione | campi | bundle | soglia della commissione |
|---|---|---|---|---|
| 0 | None | — | obbligatorio | BUNDLE_BASE = 0,001 RAND |
| 1 | Mint | cm, pk, time, r, envelope, amount, minter: PublicKey, signature | deve essere assente | 0 (firmata dal validatore; solo catene faucet, amount ≤ 100 RAND) |
| 2 | Deploy | base_pc: u32, words: Vec<u32>, public: Vec<u32> | obbligatorio | base + 100.000 unità per ogni parola di programma e ogni parola pubblica |
| 3 | Call | program, proof, input_envelope: Option<CallEnvelope> | obbligatorio | su chain 20, base + gas_price × il GAS_LIMIT dichiarato dalla prova + byte_price per KiB di prova e di envelope di input; base + call_fee(tier) su una chain senza sezione gas |
| 4 | Bond | validator, amount, registration: Option<Registration> | obbligatorio, e burn_r == amount | base |
| 5 | Unbond | validator, amount, nonce, signature | deve essere assente | 0 |
| 6 | Withdraw | validator, amount, nonce, time, r, envelope, signature | deve essere assente | 0 sul wire; la base viene prelevata da amount |
| 7 | BridgeAttest | attestation, recipient: ShieldedAddress, r, time, asset, envelope, pq_signatures: Vec<PqSignature> | obbligatorio | base |
| 8 | BridgeBurn | asset, amount, relayer_fee, to_chain: u16, token: [u8; 32], to: [u8; 32] | obbligatorio, con burn_asset == asset e burn_a == amount | BRIDGE_BURN_FEE = 0,01 RAND |
| 9 | RegisterAggregator | registration | obbligatorio, burn_r = il bond | base |
| 10 | UnbondAggregator | aggregator, nonce, signature | deve essere assente | 0 |
| 11 | WithdrawAggregator | aggregator, nonce, time, r, envelope, signature | deve essere assente | 0 |
| 12 | SlashAggregator | due header aggregati firmati | deve essere assente | 0 |
| 13 | Aggregate | covers, proof, aggregator, nonce, time, r, envelope, signature | deve essere assente | 0 |
| 14 | RegisterToken | name, symbol, decimals: u8, authority: MintAuthority, initial: Option<InitialMint>, salt: [u8; 32], index: u32 | obbligatorio | base + la registration_fee del registro (1 RAND su chain 20, bruciato) |
| 15 | TokenMint | asset, amount, recipient: ShieldedAddress, r, time, envelope, nonce, signature | obbligatorio | base |
| 16 | SetAuthority | asset, new: Option<PublicKey>, nonce, signature | obbligatorio | base |
| 17 | TokenBurn | asset, amount | obbligatorio, con burn_asset == asset e burn_a == amount | base |
| 18 | PauseMints | nonce, signature | deve essere assente | 0 |
| 19 | UnpauseMints | nonce, pq_signatures | deve essere assente | 0 |
| 20 | RegisterBridgedToken | name, symbol, salt, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatures | obbligatorio | base + registration_fee |
| 21 | ListBacking | token_index, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatures | obbligatorio | base |
| 22 | RotatePqGuardians | new_pq_guardians: Vec<PublicKey>, nonce, pq_signatures | deve essere assente | 0 |
| 23 | RotatePauseKey | new_pause_key: PublicKey, nonce, pq_signatures | deve essere assente | 0 |
| 24 | ClaimVested | entry: [u8; 32], amount, nonce, to: ShieldedAddress, time, r, envelope, signature | deve essere assente | 0 sul wire; la base viene prelevata da amount |
| 25 | RevokeVesting | entry: [u8; 32], unvested, nonce, to: ShieldedAddress, time, r, envelope, signatures (dalla v0.6.8 una soglia dei revocatori della voce, che paga la tesoreria indicata dalla sua voce di genesi; prima, una sola signature) | deve essere assente | 0 sul wire; la base viene prelevata da unvested |
| 26 | BondVested | entry: [u8; 32], validator, amount, registration: Option<Registration>, nonce, signature | deve essere assente | 0 |
| 27 | UnbondVested | entry: [u8; 32], amount, nonce, signature | deve essere assente | 0 |
| 28 | AdmitValidator | candidate: PublicKey, signatures: Vec<(PublicKey, Signature)> | deve essere assente | 0 |
| 29 | SlashEquivocation | first, second: due header di blocco firmati dello stesso leader per la stessa view | deve essere assente | 0 |
| 30 | RotatePqGuardiansV2 | new_pq_guardians: Vec<PublicKey>, possession: Vec<Vec<u8>>, nonce, pq_signatures | deve essere assente | 0 |
| 31 | RotatePauseKeyV2 | new_pause_key: PublicKey, possession: Vec<u8>, nonce, pq_signatures | deve essere assente | 0 |
| 32 | CancelRotation | kind: u8, nonce, signature | deve essere assente | 0 |
| 33 | Invoke | program, proof, input_envelope: Option<CallEnvelope>, transition: Transition | obbligatorio; burn_r e burn_a di burn_asset sono ciò che entra nel programma | la soglia di una call + cell_fee per ogni cella creata |
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
Su chain 20 sono utilizzabili i tag da 0 a 8, da 14 a 21, 28 e da 30 a 33. Il suo genesis ha una sezione bridge con rules_v2, una sezione tokens, staking.admission_by_vote (tag 28), bridge.rotation (tag da 30 a 32) e program_state (tag 33). I tag 22 e 23 vi sono rifiutati: con bridge.rotation.needs_possession una rotazione deve avere la forma …V2, che porta la firma propria di ciascuna nuova chiave. Su chain 19, che non aveva nessuna di queste sezioni, erano utilizzabili i tag da 0 a 8 e da 14 a 23 (chain 14, senza rules_v2, rifiutava 22 e 23). SlashEquivocation richiede una sezione staking.slashing, e quella di chain 20 è nulla. Le cinque azioni di aggregazione richiedono una sezione aggregation, che chain 20 non ha (una build rifiuta di avviarsi su un genesis che ne contenga una finché l’aggregazione non verrà misurata di nuovo). Un nodo rifiuta tutte queste azioni al gate prima di qualsiasi altro controllo, ma esse occupano comunque i loro tag bincode, per cui un encoder che codifica i numeri di tag in modo fisso deve contarle. L’aggregazione è documentata come in arrivo in Prove. Le quattro azioni di vesting al genesis (tag da 24 a 27, nella build dalla v0.5.11) richiedono una sezione vesting, che nessuna chain ha mai avuto, chain 20 inclusa, e sono rifiutate all’ammissione in sua assenza. I tag da 28 a 33 sono nuovi nella v0.6.8 e sono aggiunti in coda, così la storia di ogni chain precedente si decodifica ancora.
Le quattro azioni RPL (tag da 14 a 17) registrano un token, ne coniano altro se è autorizzato da una Key, cedono o rinunciano alla sua autorità di mint, e bruciano una posizione; un RegisterToken che indichi un’autorità Bridge o Program è rifiutato. Non esiste un’azione di trasferimento di token: un trasferimento di token è un None, indistinguibile da un pagamento in RAND. Vedi Token RPL. Le quattro azioni di governance del bridge (tag da 18 a 21) sono la pausa del mint, la sua revoca, e l’inserimento in elenco di un token bridged o di un ulteriore backing dopo il genesis. PauseMints è firmata dalla chiave di pausa del genesis, che può solo mettere in pausa; le altre tre richiedono un quorum dei guardiani Dilithium2 (PQ). Anche un BridgeAttest porta un quorum di questo tipo, accanto a quello ECDSA. I tag 22 e 23, con rules_v2 del bridge, sostituiscono l’insieme dei guardiani PQ o la chiave di pausa, ciascuno con un quorum PQ dell’insieme corrente e il rotation_nonce del bridge. I tag 30 e 31 fanno lo stesso con bridge.rotation e portano inoltre la firma Dilithium2 propria di ciascuna nuova chiave sul messaggio di rotazione, una prova che la chiave è detenuta; su chain 20 una rotazione viene registrata come in sospeso e ha effetto dopo delay_secs, 86.400 s. CancelRotation (tag 32), firmata dalla chiave di pausa corrente, annulla una rotazione in sospeso prima che abbia effetto. Vedi Bridge.
AdmitValidator (tag 28) è il voto dell’insieme dei validatori per ammettere una nuova chiave: firme di validatori che detengono più di due terzi dello stake, in ordine crescente di indirizzo. Con staking.admission_by_vote (chain 20) un Bond che registrerebbe un nuovo validatore è rifiutato a meno che il suo indirizzo non sia stato ammesso in questo modo (Staking). Invoke (tag 33, RPL-2) è una call la cui prova garantisce una transition di stato dichiarata del programma: le celle lette e scritte, ciò che i campi di burn del bundle portano nel vault del programma, e le note che il vault paga o che il programma conia. Il ledger applica la transizione se ogni lettura dichiarata è ancora valida e se entrambe le prove sono verificate (Programmi).
Un Deploy e una Call sono ordinarie transazioni schermate il cui bundle è un auto-trasferimento di zero: esiste per pagare la soglia con le note del chiamante, così la chain non apprende mai di chi fossero. RAND esce dal pool solo tramite burn_r — lo stake di un Bond, il bond di un RegisterAggregator, il deposito di un Invoke nel vault di un programma — e un token solo tramite burn_a con burn_asset che lo indica, in un TokenBurn, un BridgeBurn o un Invoke. Ogni altra azione non brucia nulla, e un bundle con burn_asset = 0 e burn_a diverso da zero è rifiutato ovunque.
Ordine di ammissione
Ledger::validate_inner controlla le cose economiche prima di quelle costose, in questo ordine; il mempool esegue lo stesso controllo prima di fare gossip, così una transazione errata viene rifiutata una sola volta, al margine.
- Limiti di dimensione: l’intera transazione ≤
max_block_bytesdella catena; ciascuna delle quattro envelope del bundle, e ogni envelope di nota che l’azione trasporta, esattamenteenvelope_bytesdel genesis (1.860 su chain 20; al massimo 2.048 byte su una catena senza il campo); la prova del bundle, la prova di autorizzazione e la prova di una chiamata ciascuna ≤max_proof_bytes(2 MiB per impostazione predefinita, 4 MiB nel genesis di chain 20; erano 8 MiB su chain 13 fino a 16); un programma ≤ il limite di programma della catena (4.096 parole per impostazione predefinita, 65.535 nel genesis di chain 20); un’envelope di chiamata ≤max_call_envelope_bytes(18.432 byte per impostazione predefinita, 65.536 su chain 20); un’attestazione ≤ 16 KiB. Una prova del bundle nella forma con marcatore sigillato (potata) viene rifiutata qui al di fuori della sincronizzazione in forma sigillata (PrunedFormOutsideSync), prima di qualsiasi lavoro sulla prova. - L’id della catena coincide.
- Forma e soglia minima della commissione: un’azione senza bundle non porta alcun bundle, e ogni altra ne porta esattamente uno;
check_burn_shapestabilisce quale dei tre campi di burn l’azione può impostare: unTokenBurno unBridgeBurnbrucia il proprioasseteamounttramiteburn_asseteburn_a, unBondo unRegisterAggregatorbrucia il proprio importo tramiteburn_r, e ogni altra azione non brucia nulla;fee ≥ fee_floor(action). Su una catena con una sezionegas(chain 20), in questo passaggio la soglia di unaCallo di unInvokeè la regola del gas con un limite dichiarato pari a 1,BUNDLE_BASE + gas_price + byte_price × ⌈bytes / 1024⌉calcolata sulla sua prova e sulla sua envelope di input, entrambe note prima di qualsiasi verifica. - Ancora: una delle ultime 1.024 radici di fine blocco su chain 20, il cui genesis imposta
proof_window_blocks: 1024(v0.6.8); una delle ultime 256 su una catena senza il campo, come fino a chain 19. - Tempo: entro
[height − 1024, height]su chain 20;[height − 256, height]senza il campo. L’unico valore del genesis imposta entrambe le finestre. - Nullifier e impegni: tutti e quattro i nullifier sono distinti e nessuno è speso; tutti e quattro gli impegni sono distinti e nessuno è già una foglia.
- Controlli dell’azione: il faucet è abilitato, il mint è sotto il limite e firmato da un validatore e, su chain 20, indica un destinatario presente nella allowlist
staking.faucet_recipientsdel genesis, rientra nelfaucet_budget_per_epochdell’epoca ed è firmato da un validatore presente nell’elencostaking.faucet_mintersdel genesis (una regola di validità dalla chain 16); un mint o un deposito pari o superiore a 2⁶³ viene rifiutato su ogni catena; il programma si decodifica; il programma esiste; le regole di staking (registrazione presente esattamente quando il validatore è sconosciuto, il nonce, la firma, stake sufficiente); le regole proprie del bridge perBridgeAttesteBridgeBurne per le azioni di pausa e di listing; le regole di RPL per le sue quattro azioni, inclusa la commissione di registrazione; su chain 20 il quorum del voto di ammissione e, per unInvoke, la forma della transizione, le letture dichiarate rispetto alle celle memorizzate, la copertura del vault per ogni pagamento e la commissione per cella. Tutto questo avviene prima della prova del bundle, perciò una transazione che non può essere applicata non costa alcuna verifica STARK. Conhardening_v6(chain 16 fino a 20) qui si trovano altri due rifiuti: unDeploydi un programma che nessuna chiamata potrebbe contenere (ProgramUncallable), e una prova del bundle o di una chiamata la cui intestazione o il cui transcript contiene un campo che il prover onesto non scriverebbe (non-canonical proof: …), rifiutata prima che una delle due prove venga verificata. Dalla v0.6.8 una prova la cui intestazione dichiara un grado superiore al limite della tabella più alta viene rifiutata per prima, con lo stesso verdetto (audit v6 ZKV-4), prima che il controllo la percorra. - Digest del bundle: su una catena ad autorizzazione separata (ogni catena dalla 17) si controllano prima i campi di autorizzazione: un bundle senza prova di autorizzazione viene rifiutato, il
cche la prova di autorizzazione pubblica (letto, non ancora verificato) deve essere uguale adauth_commite, su chain 20, la prova di autorizzazione deve dichiarare unGAS_LIMITpari esattamente a 1.279. Poi il ledger ricalcola il digest dei campi pubblicati del bundle,auth_commitcompreso, e questo deve essere uguale a quanto la prova ha pubblicato. Su chain 20 anche la prova del bundle deve dichiarare unGAS_LIMITpari esattamente albundle_gas_limitdel genesis, 20.479. - Prova del bundle:
Machine::verify_publicrispetto all’hc_bundlefissato nel genesis e al binding della transazione. Su una catena ad autorizzazione separata la prova di autorizzazione viene verificata subito dopo, rispetto all’hc_authfissato nel genesis e allo stesso binding. - Prova della chiamata e relativa commissione di fascia: per una
Callo unInvoke, l’intestazione della prova viene fissata per prima (fascia al massimo 14, una tabella keccak al massimo 2¹², una tabella sha256 al massimo 2¹³, l’altezza del programma distribuito, con un minimo di 2⁷ righe sottohardening_v6e, su chain 20, unGAS_LIMITdichiarato non superiore al limite massimo dell’intestazionegas_max), poi lo STARK della chiamata rispetto alcode_hashdel programma e, sottohardening_v6, al binding di chiamata della transazione. La commissione viene controllata per ultima, perché è conoscibile solo dopo che la prova è stata decodificata: su chain 20fee ≥ BUNDLE_BASE + gas_price × GAS_LIMIT + byte_price × ⌈bytes / 1024⌉ai prezzi in vigore, piùcell_feeper ogni cella creata per unInvoke(Commissioni); su una catena senza sezionegasfee ≥ BUNDLE_BASE + call_fee(tier).
Gli errori tornano da rand_sendTransaction con codice -32000 e la motivazione: nullifier already spent, anchor is not one of the last 1024 roots (256 su una catena senza 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 e, dalla chain 14, the bundle burns asset 2 on an action that burns no token, a burn of 5 is not allowed on this action, the bundle burns 399, not the 400 the action declares, wrong mint nonce: expected 1, got 0, wrong token index: expected 3, got 2, dalla chain 17 this chain requires an auth proof on every bundle (genesis hc_auth) e the auth proof publishes a different commitment than the bundle's auth_commit, e su chain 20 envelope is 1348 bytes, this chain requires exactly 1860 e bundle proof declares gas limit …, this chain requires exactly 20479. Accettazione non significa inclusione: interroga rand_getTransaction finché non restituisce un blocco.
Un blocco che contiene anche una sola transazione non valida è non valido. Il mempool indicizza ogni nullifier e ogni impegno in sospeso e rifiuta senza appello un conflitto, perciò due bundle che spendono la stessa nota non condividono mai un blocco.
Dimensioni
| parte | byte |
|---|---|
Word8 | 32 |
una Envelope | 1.892 codificati (1.860 di testo cifrato) dalla chain 18; 1.380 (1.348) fino a chain 17 |
Bundle senza le sue due prove | 7.936 (quattro nullifier, quattro impegni, quattro envelope, auth_commit) |
| transazione di trasferimento senza le sue due prove | 7.949 |
| transazione di deploy senza le sue due prove, programma di 100 parole, nessun input pubblico | 8.369 |
| transazione di chiamata senza le sue tre prove, nessuna envelope di input | 7.990 |
| transazione di mint (senza bundle) | 5.761 (una chiave Dilithium2 da 1.312 byte e una firma da 2.420 byte) |
| una prova del bundle, fascia 14, profilo di produzione | 1.497.544 e 1.504.260 letti su chain 18 (un deploy all’altezza 97.821 e una chiamata a 98.335, 2026-09-30); 1.422.116 e 1.423.170 misurati su chain 14 (la registrazione di zUSD e un listing di backing, 2026-09-20) |
| una prova di autorizzazione, fascia 10, profilo di produzione | 1.370.827 e 1.367.303 nelle stesse due transazioni di chain 18 |
Le prime sei righe sono la codifica dalla chain 18: il campo memo ha aggiunto 512 byte a ogni envelope, e l’autorizzazione separata ha aggiunto 40 byte al bundle prima dei byte propri della prova di autorizzazione. Dunque un trasferimento schermato sulla rete pesa circa 2,88 MB dalla chain 18, chain 20 inclusa, quasi interamente prova, qualunque asset sposti: circa 1,5 MB di prova del bundle e 1,37 MB di prova di autorizzazione. Era circa 1,43 MB fino a chain 15, con una sola prova. I blocchi sono limitati a 2.000 transazioni (MAX_BLOCK_TXS) e al max_block_bytes della catena in byte di transazioni, entrambe regole di consenso: 4 MiB per impostazione predefinita (MAX_BLOCK_BYTES), che conterrebbe un solo trasferimento di questo tipo, e 20 MiB nel genesis di chain 20, che ha spazio per sette. Un genesis con hc_auth deve impostare max_block_bytes ad almeno tre volte max_proof_bytes più 1 MiB, poiché una transazione può portare tre prove (bundle, autorizzazione, chiamata), perciò il valore predefinito di 4 MiB vi viene rifiutato. L’aggregazione a livello di blocco è il rimedio in coda; vedi Prove.
Rileggerne una
rand_getTransaction(hash) restituisce null finché la transazione non è inclusa, poi la sua posizione nel blocco e soltanto i campi pubblici:
{
"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 trasferimento non contiene i campi from, to, nonce, importo né asset, e nemmeno il blocco memorizzato ne contiene: il nodo non ha altro da oscurare. auth_commit e auth_proof_bytes (la lunghezza della prova di autorizzazione) sono presenti dalla chain 17. Il bundle ha sempre quattro slot, dummy inclusi, e un trasferimento di RAND e un trasferimento di qualsiasi token RPL sono entrambi "kind": "none". Ogni importo u64 è una stringa decimale dalla chain 14 (fee, burn_a, burn_r, l’amount di un’azione); indici, nonce, altezze e lunghezze restano numeri JSON. Gli altri tipi di azione mostrano i loro campi pubblici — 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, i quattro tipi di vesting claim_vested, revoke_vesting, bond_vested, unbond_vested e, dalla v0.6.8, admit_validator, slash_equivocation, rotate_pq_guardians_v2, rotate_pause_key_v2, cancel_rotation e invoke — come elencato nel riferimento JSON-RPC.