Questa pagina serve a mettere un programma on chain e a chiamarlo: che cos’è un programma, come costruirlo, che cosa pubblica e che cosa nasconde una chiamata, come rileggere una ricevuta e i limiti che incontrerai. Ogni programma è codice pubblico; ogni chiamata è una prova. Programmi di esempio contiene applicazioni complete, e randprotocol/example-programs contiene il sorgente dei programmi distribuiti su chain 20, compresi quelli RPL-2 (sotto).
Che cos’è un programma
Un programma è codice macchina RV32IM per la Rand zkVM: { base_pc: u32, words: Vec<u32> }, al massimo quanto il limite di programma della chain (65.535 parole su chain 20, come su ogni chain a partire da chain 13; 4.096 su un genesis senza max_program_words, come chain 12), con base_pc multiplo di 4 e ogni parola decodificabile come istruzione (una parola che non si decodifica viene rifiutata al deploy, mai in esecuzione). Chain 20, come chain 19, rifiuta anche il deploy di un programma che nessuna chiamata potrebbe contenere: più di 8.180 parole senza segmento pubblico, meno con uno (sotto). È indirizzato per contenuto e immutabile:
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
Una chiamata è una prova STARK che il programma con digest hc è stato eseguito su alcuni input privati, ha speso non più di un limite di gas dichiarato e ha pubblicato otto parole di output. Il nodo verifica la prova, registra una ricevuta e nient’altro: gli output sono dati, non effetti. Una chiamata non può muovere RAND; chi chiama sposta valore nel bundle che paga la chiamata, e gli output di un programma significano ciò che il programma e chi lo chiama concordano che significhino. Su chain 20 un Invoke è l’eccezione: la sua prova garantisce una transizione di stato dichiarata, che il ledger poi applica (Stato del programma (RPL-2)).
Che cosa può fare il guest:
| gruppo | istruzioni |
|---|---|
| upper / salti | LUI AUIPC JAL JALR |
| branch | BEQ BNE BLT BGE BLTU BGEU |
| load / store | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| sistema | ECALL; mai FENCE, CSR, EBREAK, le estensioni A o C |
Le syscall, per numero in a7:
| # | syscall | effetto |
|---|---|---|
| 0 | HALT | termina l’esecuzione |
| 1 | WRITE_OUTPUT slot word | fissa l’output pubblico slot < 8; ogni slot al più una volta, gli slot non scritti valgono zero |
| 2 | READ_INPUT idx | restituisce la parola di input privato idx, legata all’impegno salato H_IN |
| 3 | POSEIDON2 ptr n | calcola in place l’hash di n ≤ 4096 parole con la spugna Poseidon2 |
| 4 | KECCAK ptr | una permutazione Keccak-f[1600] in place; fa sì che la prova porti la tabella keccak |
| 5 | SHA256 ptr | una compressione SHA-256 in place; fa sì che la prova porti la tabella sha256 |
| 6 | READ_PUBLIC idx | restituisce la parola idx del segmento pubblico; su chain 20 il segmento pubblico è fissato al deploy (rand program deploy --public); su un genesis senza max_program_public_words è vuoto e questa syscall non legge nulla |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 con la lunghezza del messaggio legata alla spugna, così [a] e [a, 0] hanno hash diversi (insieme di vincoli 7, chain 16 e successive). rand-guest check ai circuits aeacf31 la rifiuta ancora (sezione successiva) |
Memoria: 32 registri con x0 a zero; RAM che parte da zero; ogni indirizzo sotto 2³⁰; uno stack di 64 KiB per i guest compilati. Un programma costruito con rand-guest porta i propri dati di sola lettura e inizializzati nel contenitore immagine, e un prologo di caricamento, parte del programma, scrive quei dati prima che giri il codice del programma (L’ISA RISC-V). Impegni sulle note, nullifier e verifiche Merkle non sono syscall ma routine di libreria su POSEIDON2.
Da chain 18 (insieme di vincoli 8) ogni esecuzione è misurata in gas. Un’istruzione ordinaria e le syscall 0, 1, 2 e 6 costano 1 gas ciascuna, POSEIDON2 su n parole costa 3 + 3 × ⌈n / 4⌉, una permutazione KECCAK costa 192 e una compressione SHA256 costa 64. Programmi di esempio contiene la tabella e ciò che dichiara una chiamata.
Costruirne uno
Tre modi, tutti producono parole RV32IM:
L’assembler integrato. Il wallet include cinque guest assemblati a mano e li assembla in un file JSON { "base_pc", "words" }:
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
Programmi compilati, con rand-guest. Il rand-guest del repository dei circuits compila un crate Rust no_std (riscv32im-unknown-none-elf, Rust 1.98.1) o una directory C (clang 23.1.1) in un contenitore immagine, controlla ogni parola con il decodificatore della macchina e stampa hc e l’id del programma. La build è ermetica, quindi lo stesso sorgente dà lo stesso hc su qualsiasi macchina. Per le chain da 18 a 20 compila la toolchain al commit dei circuits aeacf31 (branch feat/v067-circuits), il commit che fullnode v0.6.7 e v0.6.8 fissano entrambi. A quel commit rand-guest check elenca solo le syscall da 0 a 6, perciò un guest che chiama guest_sdk::poseidon2_len (syscall 7) viene rifiutato in fase di build benché la macchina la esegua (zkp-circuits issue #1). Passo per passo: Distribuire un programma Rust e Distribuire un programma RISC-V.
Programmi tradotti. sbpf2rv traduce un ELF sBPF di Solana, ed evm2rv traduce il bytecode runtime EVM, in programmi i cui output coincidono parola per parola con quelli dei guest interpreti di Rand: Il transpiler Solana e Il transpiler Ethereum.
Il wallet stesso non ha un compilatore. rand program deploy accetta un .json, un .bin grezzo di parole little-endian, o il contenitore immagine scritto da rand-guest (fullnode v0.3 e successivi).
Distribuzione
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
La testnet è chain 20, e per distribuire, chiamare o invocare su di essa serve il wallet rand di fullnode v0.6.8 o successivo. Un wallet più vecchio non può inviare affatto: il genesis di chain 20 imposta binding_domain: 1, quindi ogni binding di prova e ogni messaggio firmato include l’hash del genesis, mentre i binding di un wallet v0.6.7 portano il solo id della chain. L’endpoint pubblico https://rpc.randprotocol.org serve la sincronizzazione, i deploy e le chiamate di un wallet (configurazione).
Il wallet stampa prima l’id del programma, il numero di parole e hc, e chiede al nodo la commissione con rand_estimateFee, che rifiuta un programma oltre il limite della chain prima di qualsiasi prova. Poi costruisce un bundle di auto-trasferimento a valore zero (l’unico bundle di asset nascosti 4-in-4-out che ogni transazione porta; la commissione esce dai suoi slot RAND) che paga 0.001 RAND + 100,000 units × words con le proprie note (le parole del segmento pubblico contano come parole di codice), allega Action::Deploy { base_pc, words, public }, prova il bundle rispetto al binding della transazione e invia. Da chain 17 sono due prove, e il wallet stampa una riga per ciascuna: un’autorizzazione di spesa di tier 10 (secondi, circa 1,37 MB) e il bundle di tier 14 (circa due minuti su un laptop, circa 1,5 MB). Prima di chain 17 era la sola prova del bundle, circa 100 s e circa 1,42 MB su chain 15. La commissione di deploy non ha termini di gas o di byte: il gas prezza solo le chiamate. Il nodo controlla che ogni parola si decodifichi, calcola hc, memorizza il record e avvia in background la costruzione della chiave del verificatore. Il codice è pubblico; chi l’ha pagato no: rand_getProgram non ha un campo deployer. Distribuire due volte le stesse parole è idempotente.
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
Chiamata
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
Il wallet recupera dal nodo il codice e il segmento pubblico e verifica che il loro hash coincida con l’id. Esegue la chiamata nell’emulatore e stampa il gas e la commissione prima di provare qualsiasi cosa:
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
Poi sigilla la trascrizione di input (sotto), prova la chiamata in locale con il profilo FRI della chain usando gli input come vettore privato, prova il bundle che paga la commissione, invia, attende e stampa la ricevuta. Su chain 20 la prova della chiamata è legata alla propria transazione (un binding che include l’hash del genesis) e il bundle è composto da due prove, quindi una chiamata ne richiede tre: la chiamata, l’autorizzazione di spesa di tier 10 e il bundle di tier 14. Le due righe qui sopra provengono dalla chiamata del voto anonimo su chain 18 il 2026-09-30 (Un voto anonimo).
La commissione. Sulla chain 20 il minimo è BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, dove BUNDLE_BASE vale 0,001 RAND, i byte sono la prova della chiamata più l’involucro dell’input, e i prezzi sono partiti da 100 e 800 unità (un’unità è 10⁻⁹ RAND) e si muovono con il riempimento dei blocchi. La chain 20 li limita a 10.000 e 80.000 unità, e al suo carico contribuiscono solo i byte che pagano il prezzo per byte (le prove e gli involucri delle chiamate) (byte_load: "paying"); sulle chain 18 e 19 i prezzi non avevano un tetto e i byte di ogni transazione facevano muovere il prezzo per byte. Lo schema fisso del ledger, 0.001 RAND + call_fee(tier), la regola della chain 15, qui non si applica. Il wallet fissa la commissione prima che la prova esista, quindi valuta la prova al limite della chain di 4 MiB e aggiunge due scatti di prezzo di margine. Quattro chiamate misurate sulla chain 18 sono costate da 0,0057 a 0,0075 RAND ciascuna (il calcolo). rand fee call <tier> --gas <limit> --bytes <B> chiede direttamente al nodo il minimo. private_payment legge quattro saldi privati e, se la loro somma raggiunge almeno la sua soglia, pubblica l’eccedenza: output [1, 0, 25, 0, 0, 0, 0, 0]. La chain vede la prova, il tier e queste otto parole; non vede mai i saldi.
Opzioni: --tier T forza un tier; --gas-limit <N|max> dichiara un gas limit esatto o il massimo del tier al posto del valore predefinito, ossia il gas esatto arrotondato per eccesso a un multiplo di 2^(tier − 2); --fee <RAND> imposta la commissione; --expect-public <FILE> rifiuta prima di generare la prova a meno che il segmento pubblico del programma coincida con le parole di quel file; --auditor <rand1…> sigilla la trascrizione anche per un auditor indicato; --print-call-key stampa la chiave della singola chiamata; --no-envelope non pubblica alcuna trascrizione; --cuda seleziona il backend GPU (non testato su hardware e incompatibile con la sigillatura di un involucro). La chain 20 rifiuta del tutto --cuda su una chiamata: una prova di chiamata legata alla sua transazione viene prodotta sul backend CPU.
Ricevute
Una ricevuta viene memorizzata per ogni chiamata confermata e ricalcolata quando un nodo sincronizza o verifica la propria chain:
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in è l’impegno con sale della prova sugli input privati; da solo non rivela nulla. h_pub è il digest del segmento pubblico del programma fissato al deploy, il public_digest del programma, oppure null per un programma distribuito senza segmento pubblico. Non esiste alcun campo effect, e il gas limit dichiarato non compare nella ricevuta. L’explorer mostra lo stesso oggetto come receipt su GET /transactions/:hash per una call, elenca le chiamate di un programma su GET /programs/:id e filtra GET /transactions?kind=call&program=<id>.
Involucri degli input: divulgare una chiamata in seguito
Per impostazione predefinita rand call pubblica la trascrizione (salt, inputs) della chiamata sigillata in un involucro dell’input della chiamata, così che gli input possano essere mostrati in seguito senza una seconda prova:
| parte | byte | che cos’è |
|---|---|---|
body | 12 + 16 + 4·n + 16 | salt ‖ inputs sotto una nuova chiave per chiamata K_call |
to_sender | 60 | K_call avvolta con la chiave di visualizzazione in uscita di chi chiama |
kem_ct | 1.088 o 0 | un’incapsulazione ML-KEM-768 verso l’auditor, vuota se non c’è un auditor |
to_auditor | 60 o 0 | K_call avvolta con il segreto di quell’incapsulazione |
I dati associati del corpo sono H_IN, quindi un involucro trasferito su un’altra chiamata non si apre per nessuno. La chain controlla soltanto la dimensione (al massimo max_call_envelope_bytes: 65.536 byte sulla chain 20, 18.432 su un genesis privo del campo) e lo serve così com’è con rand_getCallEnvelope(tx). Lo aprono tre chiavi e nessun’altra: la chiave di visualizzazione di chi chiama (ogni chiamata fatta da quel wallet), la chiave per chiamata (quella sola chiamata), la chiave di visualizzazione dell’auditor (quella sola chiamata).
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call apre la trascrizione, verifica che input_digest(salt, inputs) == H_IN, riesegue il programma sugli input recuperati tramite l’emulatore, confronta gli output con quelli della ricevuta — e termina con codice diverso da zero se uno dei due controlli fallisce, così che uno script non scambi un avviso per un sì. --no-envelope è irreversibile: una volta perso il sale nessuno, nemmeno chi ha fatto la chiamata, può aprire quella chiamata.
Dalla v0.6.8 la riesecuzione avviene sul segmento pubblico su cui è stata prodotta la prova: le parole pubbliche del programma fissate al deploy e, con hardening_v6, il binding di chiamata di otto parole che le segue (issue #116 del fullnode). Nella v0.6.7 open-call funzionava solo per un programma distribuito senza segmento pubblico; per un programma che ne aveva uno stampava gli input e poi si fermava con re-running the program on these inputs failed: PublicIndex(0), terminando con codice diverso da zero senza eseguire nessuno dei due controlli.
Stato del programma (RPL-2)
La chain 20 è la prima chain con RPL-2, attivato dalla sezione program_state del suo genesis. Un programma ottiene tre cose che prima non aveva (docs/program-state.md del fullnode, crates/randprotocol-core/src/ledger/program_state.rs alla v0.6.8):
| celle | una mappa da una chiave di 8 parole a un valore di 8 parole, per programma. Una cella assente si legge come otto zeri; scrivere otto zeri la rimuove. Impegnate nella radice di stato |
| un vault | un saldo pubblico per asset (0 è RAND). Il valore entra tramite il bundle di un invoke ed esce sotto forma di note calcolate dalla chain |
| un token proprio | un token registrato con rand token create --program <id> (autorità di mint Program(id), nessuna offerta iniziale) viene emesso e bruciato solo dagli invoke di quel programma |
Le celle e i saldi del vault sono pubblici, come l’offerta di un token. Nulla in essi è una nota.
Invoke (Action 33) è una chiamata che dichiara anche una transizione, e la sua prova di chiamata mostra che il programma accetta esattamente quella transizione:
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
Ciò che entra è ciò che il bundle brucia: burn_r è RAND depositato nel vault; burn_a di burn_asset è un token depositato (inflow deposit) o distrutto (inflow burn, solo il token del programma stesso). Ciò che esce è una nota calcolata dalla chain per ogni payout, prima i pagamenti poi i mint, con l’importo e la pk del destinatario pubblici, come quelli di un mint. Il ledger applica la transizione solo se il programma esiste, la forma è legale, ogni cella in reads contiene ancora il valore dichiarato, il vault (dopo il deposito di questa stessa transazione) copre ogni pagamento, ogni mint riguarda il token del programma stesso e le prove si verificano. Una lettura che non corrisponde più viene rifiutata come StaleRead; il wallet rilegge e rigenera la prova.
In che cosa un invoke differisce da una call.
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| segmento pubblico su cui è prodotta la prova | public ‖ call_binding | public ‖ call_binding ‖ context, la transizione come parole |
| che cosa fa la chain in caso di successo | memorizza una ricevuta; gli output sono dati | memorizza la ricevuta e applica la transizione: scrive celle, muove il vault, aggiunge note di payout |
| valore | solo il trasferimento del bundle stesso | depositi nel vault tramite i burn del bundle; payout e mint in uscita da esso |
| dipende dallo stato | no | la prova no (il segmento è costruito dalla transazione), ma il controllo delle letture sì, all’ammissione e nel pool |
| commissione | il minimo della call | il minimo della call più cell_fee per ogni cella creata |
Un programma legge la parola di contesto i con READ_PUBLIC(public_len + 8 + i). La parola 0 è la versione (1), le parole da 1 a 4 sono i conteggi di letture, scritture, pagamenti e mint, poi burn_r, il tipo di inflow, burn_asset e burn_a, poi ogni lettura e scrittura come chiave e valore (8 parole ciascuna), poi ogni payout come asset e importo. I destinatari non sono nel contesto: il programma decide gli importi, il binding stabilisce chi viene pagato. Un programma deve vincolare ogni parola di contesto che gli viene mostrata e non deve avere alcun percorso che vada in panic, perché il panic handler di guest-sdk si ferma e un’esecuzione fermata è un’esecuzione dimostrabile.
Limiti della chain 20 (rand_getLimits.program_state):
| limite | chain 20 |
|---|---|
cell_fee | 10.000.000 unità, 0,01 RAND, per cella creata (una cella scritta con valore non nullo dove non ne era memorizzata alcuna); riscrivere o eliminare una cella è gratuito |
max_reads | 8 celle |
max_writes | 8 celle |
max_payouts | 4 note, pagamenti e mint insieme |
La regola del segmento è di solito più stringente: l’intero segmento deve rientrare nella tabella pubblica su cui è prodotta la prova di una chiamata allo stesso programma, 127 parole per un programma senza input pubblico. Restano così 119 parole di contesto, per esempio tre letture, tre scritture e quattro payout.
Comandi e RPC (wallet v0.6.8):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke controlla ogni voce di reads rispetto alla cella attiva prima di produrre la prova, e termina con codice 3 su una lettura obsoleta, prima della prova o all’inclusione, così che uno script rilegga e riprovi. rand_estimateFee con "kind": "invoke" quota la commissione. Il formato del file di transizione è in docs/cli.md del fullnode. Programmi RPL-2 completi distribuiti sulla chain 20 (amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss) e programmi RPL-1 (sealed-auction, ballot, eligibility, private-join, credit-score) si trovano in randprotocol/example-programs.
Che cosa è pubblico e che cosa è nascosto
| pubblico | nascosto |
|---|---|
l’id del programma e il suo codice; il livello; il limite di gas dichiarato; le otto uscite; H_IN; la commissione e i campi pubblici del bundle; la dimensione dell’envelope; se la chiamata ha usato Keccak o SHA-256 e, approssimativamente, con quale frequenza (l’intestazione della prova dichiara l’altezza di ciascuna tabella di hash) | gli input privati; registri, memoria, diramazioni; il numero reale di cicli e il gas esatto; il salt di H_IN; chi l’ha chiamato e con cosa ha pagato |
Due prove della stessa esecuzione sono byte diversi. Sulla chain 15 e precedenti questo non bastava: una chiamata con 31 o meno parole private produceva una tabella troppo piccola per nascondere, e i suoi input potevano essere recuperati dalla prova. Dalla v0.6 il prover porta quelle tabelle a un minimo di 128 righe, e il constraint set 7 (chain 16 e successive) rende opachi i totali per tabella che ancora trapelavano. hc è vincolante ma non nascondente: chiunque possa enumerare i programmi candidati può confermare quale sia stato distribuito.
Cosa non è ancora possibile
Come lo dichiara il fullnode:
- Nessuna chiamata tra programmi e nessuno stato al di fuori di RPL-2. Una
Callè una prova su una singola esecuzione; nulla passa da una chiamata all’altra, salvo ciò che il chiamante reimmette. Sulla chain 20 unaInvokelegge e scrive le celle e il vault del programma stesso (sopra); nessuna chain precedente alla 20 ha uno stato del programma, e una invoke non può toccare le celle di un altro programma. - Nessun trasferimento guidato dal programma al di fuori di un vault. L’effetto di tipo 1, il trasferimento a un conto pagato dal programma, è stato eliminato insieme ai conti, e le uscite di una
Callsono dichiarazioni. Sulla chain 20 unaInvokepaga dal vault del programma stesso, al massimo quattro note, con importi e destinatari pubblici. - Nessun compilatore nel wallet. I programmi sono i guest assemblati integrati, oppure immagini costruite con
rand-guestnel repository dei circuiti. - L’input pubblico è fissato al deploy, non a ogni chiamata. Il constraint set 6 ha aggiunto
READ_PUBLICeH_PUB. Sulla chain 20 un deploy può portare fino a 32.768 parole pubbliche, l’id del programma le vincola, e la prova di ogni chiamata viene verificata rispetto al loro digest. Non esistono parole pubbliche per singola chiamata. - Le syscall di hash producono prove grandi. Una prova che include keccak pesa circa 3,2 MB, ben oltre il limite predefinito di 2 MiB per le prove; una prova che include sha256 aggiunge circa 400 KB. Il limite della chain 20 è 4.194.304 byte (4 MiB, come sulle chain da 17 a 19; era 8 MiB sulle chain da 13 a 16), che ammette entrambe: sulla chain 18 una chiamata di livello 12 con la tabella Keccak ha misurato 3,35 MB e una chiamata di livello 14 con la tabella SHA-256 1,91 MB (Programmi di esempio). Una chiamata è inoltre limitata a 128 permutazioni Keccak-f e 128 compressioni SHA-256 (sotto), e ogni KiB di prova è addebitato al prezzo per byte.
- I programmi EVM e Solana non vengono distribuiti sulla chain 20, e le loro chiamate superano il limite di livello delle chiamate. I guest interprete (
evm.bin, 18.009 parole;sbpf.bin, 8.317) e le immagini tradotte (l’ERC-20, 11.686 parole; SPL Token, 65.096) superano il limite predefinito di 4.096 parole e restano sotto le 65.535 fissate dalla chain 13 e mantenute da ogni chain successiva. Entrambe le immagini tradotte sono state distribuite sulla chain 13 il 2026-09-19, SPL Token con il suo ELF come input pubblico. Le chain 14 e 15 sono state avviate da zero, e su nessuna delle due è stata ridistribuita alcuna immagine. La chain 20, come la 19, rifiuta il deploy di tutti e quattro: in base alla sua regolahardening_v6, un programma che nessuna chiamata di livello 14 potrebbe contenere, ossia più di 8.180 parole senza segmento pubblico e meno con uno, viene rifiutato al deploy (ProgramUncallable), così che non si paghi una commissione di deploy per un programma che nessuno può chiamare. Una chiamata ERC-20 viene dimostrata a livello 16 o 18: l’approvedi livello 16 è stato chiamato sulla chain 13 il 2026-09-19 con una prova di produzione di 3.412.405 byte (Transpiler Ethereum), e la chain 20 rifiuta qualsiasi chiamata oltre il livello 14 (voce successiva), quindi oggi lì nessuna chiamata ERC-20 è ammissibile. Le chiamate SPL Token sono di livello 20, che richiede circa 330 GB; non esiste alcuna prova di chiamata SPL (memoria di proving). Gli opcode EVM di contesto del blocco generano un trap, e non esiste alcuno stato EVM on-chain. - Una chiamata viene dimostrata a livello 14 o inferiore. Dalla versione v0.5.6 del fullnode (2026-09-25) una prova di chiamata oltre il livello 14 viene rifiutata (
CallTierTooHigh), così come una con più di 128 permutazioni Keccak-f o 128 compressioni SHA-256. L’approveERC-20 di livello 16 della chain 13 sarebbe rifiutato sulla chain 20, come lo sarebbe stato sulle chain 15, 18 e 19, e lo sarebbero anche tutte le chiamate ERC-20 di livello 18 o SPL Token di livello 20; queste si dimostrano ancora off chain (il budget). - Il proving su GPU non è mai stato eseguito su hardware.
--cudarichiede una build con--features cudae PTX committato; nessuno dei due esiste sulla flotta. Sulla chain 20 la prova di una chiamata viene prodotta sul backend CPU, qualunque sia la build. hcnon è nascondente, come sopra, e una richiesta di witness comunica comunque al nodo quale foglia spende un wallet.