Tipi di transazione 24, 25, 26, 27, 28 e 39. Corpi, registro delle app, pagamenti e come verificare tu stesso un esito. Trascritto dai sorgenti del nodo.
Un'app su ZooBC è un gioco o una scommessa le cui regole vengono eseguite nel consenso. Ogni mossa è una transazione, la
plancia vive nello stato della catena e la vincita viene pagata dal protocollo. Non c'è alcun server di cui fidarsi, nessun
operatore che possa rifiutarsi di pagare e nessun risultato da accettare sulla fiducia: tutto è
riproducibile dalla cronologia della catena da chiunque, per sempre.
Questo manuale è il riferimento per gli integratori: i sei tipi di transazione, i corpi esatti, il registro
delle app con la codifica delle mosse di ciascuna, come si muove il denaro e come la casualità viene derivata e verificata.
Tutto ciò che trovi qui è trascritto dai sorgenti del nodo, include/zoobc/common/types.h,
src/transaction/app_rules.cpp, e src/transaction/transaction_executor.cpp, non dalle note
di progettazione, che precedono l'implementazione e in alcuni punti se ne discostano.
1. Le tre categorie
| Categoria | Posti | Controparte | Casualità | Tipi |
|---|---|---|---|---|
| In solitario contro il banco | 1 | il pool delle app del protocollo | sì, seed del blocco | 16–21 |
| Testa a testa | 2 | un altro giocatore | solo se l'app la usa | 1–8 |
| Di gruppo | 3–4 | altri giocatori | dadi dal seed del blocco | 32–35 |
Tutte e tre condividono un unico motore: gli stessi tipi di transazione, lo stesso escrow dello stake, la stessa scadenza
per mossa e la stessa regola per cui una mossa è una transazione.
seats determina la categoria e viene confrontato con il tipo di app. Un'app in solitario deve avere seats == 1
e un tipo nell'intervallo 16–31; un'app multigiocatore deve avere seats da 2 a 4 e un tipo fuori da quell'intervallo. Un errore
su questo punto viene respinto all'ammissione nella mempool, non all'esecuzione.
2. Tipi di transazione
Sei tipi. Tutti i corpi sono little-endian; tutti gli importi sono in unità atomiche (dividi per 1e8 per ottenere ZBC).
| Tipo | Nome | Corpo |
|---|---|---|
| 24 | CreateApp | app_type(1) · stake_token_id(8) · stake_amount(8) · seats(1) · params_len(2) · params · [opponent(36)] · [channel(1)] |
| 25 | JoinApp | app_id(8) |
| 26 | AppMove | app_id(8) · move_len(2) · move_bytes |
| 27 | ResignApp | app_id(8) |
| 28 | ClaimAppTimeout | app_id(8) |
| 39 | SettleApp | app_id(8) · final_seq(4) · move_count(4) · [seat(1) · move_len(2) · move · signature(64)]* |
I due campi finali facoltativi di CreateApp
opponent e channel sono entrambi facoltativi, e quali siano presenti si deduce da quanti byte
restano dopo params:
| Byte rimanenti | Significato |
|---|---|
| 0 | app aperta, gioco on-chain |
| 1 | channel soltanto |
| 36 | opponent soltanto |
| 37 | opponent seguito da channel |
Un opponent trasforma l'app in una sfida diretta: solo quell'indirizzo può unirsi. Se lo ometti, chiunque
può occupare il posto. channel = 1 contrassegna un'app a canale di stato ed è valido solo quando seats == 2.
Le app multigiocatore memorizzano i giocatori in slot fissi da 36 byte, quindi ogni indirizzo seduto al tavolo deve essere canonico a 36 byte (un indirizzo ZBC o una semplice chiave pubblica da 32 byte). Gli account che non sono da 36 byte vengono respinti alla creazione. Le app in solitario non hanno questa restrizione: c'è un solo giocatore.
3. Il registro delle app
app_type è un singolo byte. Sono noti solo questi valori; qualsiasi altro viene respinto.
Testa a testa (2 posti)
| Tipo | App | Plancia / stato | Byte della mossa |
|---|---|---|---|
| 1 | Tris | 9 caselle | [cell 0..8] |
| 2 | Scacchi | 64 case | [from, to], legalità completa, scacco, scacco matto, stallo, promozione automatica a donna |
| 3 | Connect-4 | 42 caselle (7×6) | [column 0..6], gravità, 4 in fila |
| 4 | Dama | 64 caselle + blocco della presa multipla | [from, to], presa obbligatoria, prosecuzione della presa multipla, dame |
| 5 | Reversi | 64 caselle | [cell 0..63], bisogna girare almeno una pedina; chi non ha mosse passa; vince chi ha più pedine |
| 6 | Gomoku | 225 caselle (15×15) | [x, y], 5 in fila |
| 7 | Battaglia navale | 6400 di commitment + 200 di rivelazione | commit-reveal, vedi sotto |
| 8 | Punti e quadrati | 24 lati + 9 quadrati | [edge 0..23], chi completa un quadrato muove ancora |
In solitario contro il banco
| Tipo | App | params | Paga |
|---|---|---|---|
| 16 | Due dadi | [bet_type 0..3, total] | sotto il 7 / sopra il 7 2.28×, 7 fortunato 5.7×, totale esatto 3420/ways % |
| 17 | Testa o croce | [choice 0/1] | 1.98× |
| 18 | Roulette | [bet_type, value] | numero singolo 36×, colore 2× |
| 19 | Slot | nessuno | tris 15×, triplo 7 50×, qualsiasi coppia 1.8× |
| 20 | Lotteria | [pick 0..99] | 90× |
| 21 | Crash | [target ×100, 2 bytes LE] | target ×, da 1.01× a 10.00× |
I tipi di puntata per i dadi sono 0 sotto il 7, 1 7 fortunato, 2 sopra il 7, 3 totale esatto. Per un totale esatto il
moltiplicatore è 3420 / ways in centesimi, dove ways = 6 - |7 - total|, quindi il 7 paga 5.70× e il 2 o il
12 pagano 34.20×. La roulette ha 37 caselle; lo 0 è verde e perde su entrambi i colori.
Di gruppo (3–4 posti)
| Tipo | App | Stato | Byte della mossa |
|---|---|---|---|
| 32 | Ludo | seats×4 posizioni delle pedine + dado in sospeso | in due fasi: tira, poi scegli una pedina |
| 33 | Pig | seats punteggi + totale del turno | [0 roll, 1 hold] |
| 34 | Corsa (scale e serpenti) | seats posizioni | [0], tira |
| 35 | Monopoli semplificato | denaro, posizioni, proprietà, bancarotta, fase | in due fasi: tira, poi eventualmente compra |
4. Denaro
Stake. CreateApp mette in escrow lo stake del creatore. Ogni JoinApp mette in escrow uno stake uguale. Il piatto
è la somma. Un stake_token_id pari a 0 indica ZBC; qualsiasi altro valore usa come stake quel token colorato, e
l'intera app (piatto, rake, vincita) viene regolata in quel token.
Rake. In caso di esito decisivo, l'1% del piatto va al pool delle app e il vincitore riceve il
resto. In caso di pareggio o di app annullata, ogni stake viene rimborsato e non si trattiene alcun rake.
Il pool delle app è un account di protocollo con un saldo per ciascun token. Cresce grazie a quel rake dell'1% più
il vantaggio del banco nelle app in solitario. È la controparte di ogni puntata in solitario, quindi è il suo saldo a rendere
possibile il gioco in solitario; puoi consultarlo in qualsiasi momento:
GET https://zoobc.network/api/v1/apps/pool
Tetto del bankroll. La vincita massima possibile di una singola puntata in solitario non può superare il 5% del pool per
quel token. Il controllo si basa sul caso peggiore della puntata per il banco, non sulla sua media, quindi nessuna
singola vincita può prosciugare il pool. Una puntata che lo supererebbe viene respinta: se una puntata elevata viene rifiutata,
di solito il motivo è questo.
5. La casualità, e come verificarla
Le app in solitario si risolvono a partire dal seed del blocco, che su ZooBC è
block_seed = blocksmith_signature(SHA3(previous_block.block_seed)), deterministico, verificabile
pubblicamente con la chiave pubblica del blocksmith e impossibile da conoscere prima che il blocco esista.
Una puntata inserita nel blocco all'altezza H si risolve a H + 2:
r = SHA3-256( block_seed[H+2] ‖ app_id ) → the low 8 bytes, little-endian, as a uint64
Il giocatore si impegna due blocchi prima che esista il seed con cui la puntata verrà risolta, quindi non può
né prevederlo né sceglierlo. Chiunque può ricalcolare r in seguito a partire dal seed del blocco e dall'id dell'app, e
quindi ricalcolare l'esito. Nulla del risultato dipende da dati che la catena non contiene.
Crash ricava il suo punto di crash dallo stesso r:
u = r mod 1e6
C = 99_000_000 / (1_000_000 - u) clamped to [100, 1000], i.e. 1.00× .. 10.00×
La puntata vince stake × target / 100 quando C >= target. Il 99 al numeratore è il vantaggio
del banco, l'1%. Il limite di 10.00× è il tetto, quindi la vincita massima possibile a Crash è dieci volte la puntata.
Giocata multipla. Qualsiasi app in solitario può essere giocata fino a 100 volte con una sola puntata. Aggiungi in coda un byte
di conteggio N a params, dopo i byte di scelta di quell'app. Lo stake viene diviso in parti uguali tra le N giocate
e le vincite vengono sommate, così una transazione e una rivelazione producono N esiti indipendenti senza
attendere un blocco per ciascuno. Omettere N significa una singola giocata, e una singola giocata è identica bit per bit a come
sarebbe stata prima che esistesse la giocata multipla. N deve essere compreso tra 1 e 100 e lo stake deve dividersi in modo che ogni giocata
punti almeno un'unità.
6. Turni, scadenze e conclusioni
Un'app diventa attiva quando viene occupato l'ultimo posto. Da quel momento ogni mossa accettata imposta
deadline_height = current_height + 240 (~1 hour at 15 s per block)
Una mossa viene accettata solo dal giocatore di turno, solo mentre l'app è attiva e solo
prima che scada il termine. Un'app può concludersi in quattro modi:
- Una vittoria o un pareggio secondo le regole. Regolata immediatamente: il vincitore viene pagato, oppure, in caso di pareggio, tutti gli stake vengono rimborsati.
ResignApp. Abbandoni; la tua quota del piatto va all'avversario.ClaimAppTimeout. Il tuo avversario ha lasciato scadere il termine di 240 blocchi. Lo rivendichi e vinci il piatto.
Sta a te rivendicarlo: non succede nulla in automatico solo perché il termine è scaduto.
- Nessuno si unisce. Un'app aperta che nessuno accetta viene annullata e lo stake rimborsato.
7. Canali di stato e SettleApp
In un'app testa a testa, giocare ogni mossa on-chain costa una transazione e un blocco per mossa. Un
canale di stato sostituisce tutto questo con un unico regolamento: crea con channel = 1 e seats = 2, gioca
off-chain con ciascuna parte che firma ogni mossa e invia l'intera partita una sola volta come SettleApp (tipo 39).
La catena riesegue le mosse firmate, in ordine, a partire dallo stato iniziale dell'app attraverso lo stesso motore
di regole, verifica ogni firma e ogni turno e paga il vincitore. Un regolamento può essere sostituito da
uno con un final_seq più alto per 240 blocchi dopo la sua inclusione; una volta trascorsa quella finestra
di contestazione, diventa definitivo.
Sul corpo vengono applicati tre limiti, e un integratore deve rispettarli tutti e tre:
final_seqdeve essere uguale amove_count.move_countnon può superare 1024 voci.move_countnon può superare ciò che il resto del corpo può fisicamente contenere. Ogni voce occupa almeno 67
byte, seat(1) + move_len(2) + signature(64), quindi un conteggio superiore a remaining / 67 viene
respinto prima che venga allocato un solo byte.
Il limite di 1024 voci si riferisce alle semimosse, non alle coppie di mosse: una voce è una semimossa, quindi un regolamento da 1024 voci copre una partita di 512 mosse. La partita a scacchi più lunga mai registrata è durata 269 mosse, cioè 538 semimosse, quindi il tetto è circa il doppio della partita più lunga mai giocata davvero.
8. Battaglia navale (tipo 7)
Battaglia navale è l'unica app testa a testa che richiede informazioni nascoste, quindi usa lo schema commit-reveal.
La plancia è 10×10. Per ciascuna delle 100 caselle il giocatore sceglie un salt casuale da 32 byte e impegna
SHA3(is_ship ‖ salt); il commitment è la concatenazione di quei 100 hash, 3200 byte, passata come
params alla creazione. seats deve valere 2 e il commitment deve essere di esattamente 3200 byte, altrimenti la creazione viene
respinta.
Il gioco alterna due tipi di mossa:
- Fuoco
[0, cell]: spari a una casella della plancia avversaria. Il turno passa all'avversario, che deve rivelare. - Rivelazione
[1, cell, is_ship, salt(32)]: l'avversario dimostra cosa c'era aprendo il commitment
di quella sola casella. Poiché ogni casella è impegnata separatamente, un giocatore non può mentire su nessuna casella.
Viene registrato se il colpo è andato a segno o a vuoto, poi tocca a chi ha rivelato sparare.
Colpisci tutte le 17 caselle delle navi e vinci. Un giocatore che non rivela è soggetto alla scadenza come per qualsiasi altra mossa,
quindi si applica ClaimAppTimeout.
Le commissioni crescono con la dimensione. Una creazione da 3200 byte è una transazione grande, e la commissione minima cresce con la dimensione della transazione. Su una catena attiva una commissione di 0.1 ZBC è stata respinta perché troppo bassa, mentre circa 5 ZBC sono stati accettati. Mettilo in conto alla creazione; le mosse di fuoco e di rivelazione sono piccole ed economiche.
9. Leggere lo stato delle app
Cinque endpoint, serviti dall'API del nodo; nota che sono endpoint del nodo, non di archivio:
URL di base https://zoobc.network/api/v1. È il gateway pubblico della TestNet di ZooBC, non la MainNet.
| Endpoint | Restituisce |
|---|---|
GET /api/v1/apps | la lobby; filtra con status=, category=solo|pvp|party, limit= |
GET /api/v1/apps/open | sfide testa a testa aperte a cui chiunque può unirsi |
GET /api/v1/apps/:id | lo stato completo di un'app |
GET /api/v1/apps/pool | i saldi del pool delle app, per token |
GET /api/v1/apps/stats | conteggi in tempo reale: totali, aperte, attive, concluse, giocatori, piatto in gioco |
Una riga di app contiene id, app_type, status (0 aperta, 1 attiva, 2 conclusa, 3 annullata), seats,
creator_address, players, opponent_address, stake_token_id, stake_amount, pot,
state_blob, turn, created_height, last_move_height, deadline_height, resolve_height,
winner_address, persist_height e channel.
Lo state_blob è la plancia derivata corrente, conservata perché un nodo non debba rieseguire la cronologia a ogni blocco.
È una comodità, non il registro: il registro sono le transazioni delle mosse, e un client può rieseguirle
per verifica o animazione. La riga attiva di un'app conclusa viene eliminata dopo un periodo di tolleranza; le sue mosse restano
per sempre nella cronologia della catena, quindi l'app è sempre ricostruibile.
10. Dalla riga di comando
zoobc-cli copre tutti e sei i tipi. Il primo parametro è sempre la chiave privata del mittente.
| Comando | Tipo |
|---|---|
app-create | 24 |
app-join | 25 |
app-move | 26 |
app-resign | 27 |
app-claim | 28 |
app-settle | 39 |
# a coin-flip against the house: type 17, 1 ZBC, seats=1, choice 0
zoobc-cli app-create <privkey> 17 0 100000000 1 00 --api $API
# tic-tac-toe, open to anyone, 1 ZBC
zoobc-cli app-create <privkey> 1 0 100000000 2 --api $API
# take the middle square
zoobc-cli app-move <privkey> <app_id> 04 --api $API
zoobc-cli help app-create elenca i campi di ciascuno. Consulta il Manuale della CLI per l'insieme completo
dei comandi e il Manuale delle transazioni per la struttura in byte di tutti i 50 tipi di transazione.
11. Costanti
| Costante | Valore | Cosa regola |
|---|---|---|
| Rake | 1% del piatto | al pool delle app, solo in caso di esito decisivo |
| Scadenza della mossa | 240 blocchi | ~1 ora; si azzera a ogni mossa accettata |
| Risoluzione in solitario | altezza della puntata + 2 | ~30 secondi |
| Tetto del bankroll | 5% del pool | vincita massima di una singola puntata in solitario |
| Tetto della giocata multipla | 100 | giocate per puntata in solitario |
| Tetto delle mosse nel regolamento | 1024 | voci per SettleApp, ≥67 byte ciascuna |
| Finestra di contestazione del regolamento | 240 blocchi | ~1 ora durante la quale prevale un final_seq più alto |
| Posti | 1, oppure 2–4 | 1 è in solitario; 2–4 è multigiocatore |
| Commitment di Battaglia navale | 3200 byte | esatti, altrimenti la creazione viene respinta |