Types de transaction 24, 25, 26, 27, 28 et 39. Corps, registre des applications, paiements, et comment vérifier vous-même un résultat. Transcrit à partir des sources du nœud.
Une application sur ZooBC est un jeu ou un pari dont les règles s'exécutent dans le consensus. Chaque coup est une transaction, le
plateau réside dans l'état de la chaîne et le paiement est effectué par le protocole. Aucun serveur auquel se fier, aucun
opérateur qui puisse refuser de payer, aucun résultat à croire sur parole : tout est
rejouable à partir de l'historique de la chaîne, par n'importe qui, pour toujours.
Ce manuel est la référence de l'intégrateur : les six types de transaction, les corps exacts, le registre
des applications avec l'encodage des coups de chacune, la circulation de l'argent, et la façon dont l'aléa est dérivé et vérifié.
Tout ce qui figure ici est transcrit des sources du nœud, include/zoobc/common/types.h,
src/transaction/app_rules.cpp, et src/transaction/transaction_executor.cpp, et non des notes
de conception, antérieures à l'implémentation et qui en diffèrent par endroits.
1. Les trois catégories
| Catégorie | Places | Contrepartie | Aléa | Types |
|---|---|---|---|---|
| Solo contre la banque | 1 | la réserve des applications du protocole | oui, graine du bloc | 16–21 |
| Face-à-face | 2 | un autre joueur | seulement si l'application l'utilise | 1–8 |
| Groupe | 3–4 | d'autres joueurs | dés tirés de la graine du bloc | 32–35 |
Les trois partagent un même moteur : les mêmes types de transaction, le même séquestre des mises, le même délai
par coup, et la même règle : un coup égale une transaction.
seats détermine la catégorie et est vérifié par rapport au type d'application. Une application solo doit avoir seats == 1
et un type dans la plage 16–31 ; une application multijoueur doit avoir seats 2–4 et un type hors de cette plage. Une erreur
sur ce point est rejetée à l'admission dans la mempool, pas à l'exécution.
2. Types de transaction
Six types. Tous les corps sont en little-endian ; tous les montants sont en unités atomiques (divisez par 1e8 pour obtenir des ZBC).
| Type | Nom | Corps |
|---|---|---|
| 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)]* |
Les deux champs finaux facultatifs de CreateApp
opponent et channel sont tous deux facultatifs, et leur présence se déduit du nombre d'octets
restant après params:
| Octets restants | Signification |
|---|---|
| 0 | application ouverte, jeu sur la chaîne |
| 1 | channel seulement |
| 36 | opponent seulement |
| 37 | opponent puis channel |
Un opponent transforme l'application en défi direct : seule cette adresse peut la rejoindre. Omettez-le et n'importe qui
peut prendre la place. channel = 1 marque une application à canal d'état et n'est valide que lorsque seats == 2.
Les applications multijoueurs stockent les joueurs dans des emplacements fixes de 36 octets : chaque adresse occupant une place doit donc être canonique sur 36 octets (une adresse ZBC ou une clé publique brute de 32 octets). Les comptes qui ne font pas 36 octets sont rejetés à la création. Les applications solo n'ont pas cette restriction, puisqu'il n'y a qu'un seul joueur.
3. Le registre des applications
app_type est un seul octet. Seules ces valeurs sont connues ; toute autre valeur est rejetée.
Face-à-face (2 places)
| Type | Application | Plateau / état | Octets du coup |
|---|---|---|---|
| 1 | Morpion | 9 cases | [cell 0..8] |
| 2 | Échecs | 64 cases | [from, to], légalité complète, échec, échec et mat, pat, promotion automatique en dame |
| 3 | Connect-4 | 42 cases (7×6) | [column 0..6], gravité, 4 alignés |
| 4 | Dames | 64 cases + verrou de prise multiple | [from, to], prise obligatoire, prises multiples enchaînées, dames |
| 5 | Reversi | 64 cases | [cell 0..63], au moins un pion doit être retourné ; un camp sans coup possible passe ; le plus de pions l'emporte |
| 6 | Gomoku | 225 cases (15×15) | [x, y], 5 alignés |
| 7 | Bataille navale | 6400 d'engagement + 200 de révélation | engagement-révélation, voir plus bas |
| 8 | Jeu des petits carrés | 24 segments + 9 carrés | [edge 0..23], compléter un carré donne un coup supplémentaire |
Solo contre la banque
| Type | Application | params | Gains |
|---|---|---|---|
| 16 | Deux dés | [bet_type 0..3, total] | moins de 7 / plus de 7 2.28×, 7 chanceux 5.7×, total exact 3420/combinaisons % |
| 17 | Pile ou face | [choice 0/1] | 1.98× |
| 18 | Roulette | [bet_type, value] | numéro plein 36×, couleur 2× |
| 19 | Machine à sous | aucune | trois identiques 15×, triple 7 50×, toute paire 1.8× |
| 20 | Loterie | [pick 0..99] | 90× |
| 21 | Crash | [target ×100, 2 bytes LE] | cible ×, de 1.01× à 10.00× |
Les types de pari aux dés sont 0 moins de 7, 1 7 chanceux, 2 plus de 7, 3 total exact. Pour un total exact, le
multiplicateur est 3420 / ways en centièmes, où ways = 6 - |7 - total| : 7 paie donc 5.70× et 2 ou
12 paient 34.20×. La roulette a 37 cases ; le 0 est vert et perd sur les deux couleurs.
Groupe (3–4 places)
| Type | Application | État | Octets du coup |
|---|---|---|---|
| 32 | Petits chevaux | seats×4 positions des pions + dé en attente | deux phases : lancer, puis choisir un pion |
| 33 | Pig | seats scores + total du tour | [0 roll, 1 hold] |
| 34 | Course (serpents et échelles) | seats positions | [0], lancer |
| 35 | Monopoly simplifié | argent, positions, propriétés, faillite, phase | deux phases : lancer, puis acheter éventuellement |
4. L'argent
Mises. CreateApp place la mise du créateur sous séquestre. Chaque JoinApp place une mise égale sous séquestre. Le pot
en est la somme. Un stake_token_id de 0 signifie ZBC ; toute autre valeur mise ce jeton coloré, et
toute l'application (pot, commission, gains) est réglée dans ce jeton.
Commission. En cas d'issue décisive, 1 % du pot va à la réserve des applications et le gagnant reçoit le
reste. En cas d'égalité ou d'application annulée, toutes les mises sont remboursées et aucune commission n'est prélevée.
La réserve des applications est un compte du protocole avec un solde par jeton. Elle est alimentée par cette commission de 1 %, plus
l'avantage de la banque sur les applications solo. Elle est la contrepartie de chaque pari solo : c'est donc son solde qui rend
le jeu solo possible, et vous pouvez le consulter à tout moment :
GET https://zoobc.network/api/v1/apps/pool
Plafond de bankroll. Le gain maximal possible d'un seul pari solo ne peut pas dépasser 5 % de la réserve pour
ce jeton. La vérification porte sur le pire cas du pari pour la banque, pas sur sa moyenne : aucun
gain isolé ne peut donc vider la réserve. Un pari qui dépasserait ce plafond est rejeté ; si un gros pari est refusé,
c'est généralement pour cette raison.
5. L'aléa, et comment le vérifier
Les applications solo se résolvent à partir de la graine du bloc, qui sur ZooBC est
block_seed = blocksmith_signature(SHA3(previous_block.block_seed)), déterministe, publiquement
vérifiable avec la clé publique du forgeur de bloc, et impossible à connaître avant que le bloc n'existe.
Un pari placé dans le bloc à la hauteur H se résout à H + 2:
r = SHA3-256( block_seed[H+2] ‖ app_id ) → the low 8 bytes, little-endian, as a uint64
Le joueur s'engage deux blocs avant que la graine qui servira à la résolution n'existe : il ne peut donc
ni la prédire ni la choisir. N'importe qui peut ensuite recalculer r à partir de la graine du bloc et de l'identifiant de l'application, et
donc recalculer le résultat. Rien dans le résultat ne dépend de données que la chaîne ne détient pas.
Crash dérive son point de crash du même r:
u = r mod 1e6
C = 99_000_000 / (1_000_000 - u) clamped to [100, 1000], i.e. 1.00× .. 10.00×
Le pari gagne stake × target / 100 lorsque C >= target. Le 99 au numérateur est l'avantage
de la banque, 1 %. Le plafond est fixé à 10.00× : le plus gros gain possible au crash est donc dix fois la mise.
Multi-parties. Toute application solo peut être jouée jusqu'à 100 fois avec un seul pari. Ajoutez un octet de compteur
final N à params, après les octets de choix de l'application. La mise est répartie équitablement entre les N parties
et les gains sont additionnés : une transaction et une révélation produisent ainsi N résultats indépendants sans
attendre un bloc pour chacun. Omettre N signifie une seule partie, et une seule partie est identique, au bit près, à ce qu'elle
aurait été avant l'existence du multi-parties. N doit être compris entre 1 et 100 et la mise doit être divisible de sorte que chaque partie
mise au moins une unité.
6. Tours, délais et fins de partie
Une application devient active lorsque sa dernière place est occupée. Dès lors, chaque coup accepté fixe
deadline_height = current_height + 240 (~1 hour at 15 s per block)
Un coup n'est accepté que du joueur dont c'est le tour, que tant que l'application est active, et que
avant l'expiration du délai. Une application peut se terminer de quatre façons :
- Une victoire ou une égalité selon les règles. Réglée immédiatement : le gagnant est payé, ou toutes les mises sont remboursées en cas d'égalité.
ResignApp. Vous abandonnez ; votre part du pot va à l'adversaire.ClaimAppTimeout. Votre adversaire a laissé passer le délai de 240 blocs. Vous réclamez, et vous remportez le pot.
C'est à vous de réclamer : rien ne se passe automatiquement du seul fait que le délai a expiré.
- Personne ne rejoint. Une application ouverte que personne ne rejoint est annulée et la mise remboursée.
7. Canaux d'état et SettleApp
Pour une application en face-à-face, jouer chaque coup sur la chaîne coûte une transaction et un bloc par coup. Un
canal d'état remplace cela par un règlement unique : créez avec channel = 1 et seats = 2, jouez
hors chaîne, chaque camp signant chaque coup, et soumettez la partie entière en une fois sous forme de SettleApp (type 39).
La chaîne rejoue les coups signés, dans l'ordre, depuis l'état initial de l'application, avec le même moteur
de règles, vérifie chaque signature et chaque tour, puis paie le gagnant. Un règlement peut être supplanté par
un autre portant un final_seq plus élevé pendant 240 blocs après son inclusion ; une fois cette fenêtre de contestation
écoulée, il est définitif.
Trois limites s'appliquent au corps, et un intégrateur doit respecter les trois :
final_seqdoit être égal àmove_count.move_countne peut pas dépasser 1024 entrées.move_countne peut pas dépasser ce que le reste du corps peut physiquement contenir. Chaque entrée fait au moins 67
octets, seat(1) + move_len(2) + signature(64) : un nombre supérieur à remaining / 67 est donc
rejeté avant qu'un seul octet ne soit alloué.
1024 entrées est une limite par demi-coup, et non par paire de coups : une entrée correspond à un demi-coup, donc un règlement de 1024 entrées couvre une partie de 512 coups. La plus longue partie d'échecs jamais enregistrée a duré 269 coups, soit 538 demi-coups : le plafond représente donc environ le double de la plus longue partie réellement jouée.
8. Bataille navale (type 7)
La bataille navale est la seule application en face-à-face qui nécessite des informations cachées : elle utilise donc l'engagement-révélation.
Le plateau fait 10×10. Pour chacune des 100 cases, le joueur choisit un sel aléatoire de 32 octets et s'engage sur
SHA3(is_ship ‖ salt); l'engagement est la concaténation de ces 100 hachages, soit 3200 octets, transmise en tant que
params à la création. seats doit valoir 2 et l'engagement doit faire exactement 3200 octets, sinon la création est
rejetée.
Le jeu alterne deux types de coups :
- Tir
[0, cell]: vous tirez sur une case du plateau adverse. Le tour passe à l'adversaire, qui doit révéler. - Révélation
[1, cell, is_ship, salt(32)]: l'adversaire prouve ce qui s'y trouvait en ouvrant l'engagement
de cette seule case. Comme chaque case fait l'objet d'un engagement distinct, un joueur ne peut mentir sur aucune case.
Le résultat (touché ou manqué) est enregistré, puis celui qui a révélé tire à son tour.
Touchez les 17 cases de navires et vous gagnez. Un joueur qui refuse de révéler est soumis au délai comme pour tout autre coup :
ClaimAppTimeout s'applique donc.
Les frais augmentent avec la taille. Une création de 3200 octets est une grosse transaction, et le plancher des frais augmente avec la taille de la transaction. Sur une chaîne en service, des frais de 0.1 ZBC ont été rejetés comme trop bas et environ 5 ZBC ont été acceptés. Prévoyez-le pour la création ; les coups de tir et de révélation sont petits et bon marché.
9. Lire l'état d'une application
Cinq points de terminaison, servis par l'API du nœud ; notez qu'il s'agit de points de terminaison de nœud, et non d'archive :
URL de base : https://zoobc.network/api/v1. Il s'agit de la passerelle publique du TestNet ZooBC, pas du MainNet.
| Point de terminaison | Renvoie |
|---|---|
GET /api/v1/apps | le salon d'attente ; filtrez avec status=, category=solo|pvp|party, limit= |
GET /api/v1/apps/open | les défis en face-à-face ouverts que tout le monde peut rejoindre |
GET /api/v1/apps/:id | l'état complet d'une application |
GET /api/v1/apps/pool | les soldes de la réserve des applications, par jeton |
GET /api/v1/apps/stats | compteurs en direct : total, ouvertes, actives, terminées, joueurs, pot en jeu |
Une ligne d'application contient id, app_type, status (0 ouverte, 1 active, 2 terminée, 3 annulée), 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 et channel.
Le state_blob est le plateau dérivé actuel, conservé pour qu'un nœud n'ait pas à rejouer l'historique à chaque bloc.
C'est une commodité, pas la référence : la référence, ce sont les transactions de coups, et un client peut les rejouer
pour un audit ou une animation. La ligne en direct d'une application terminée est supprimée après un délai de grâce ; ses coups restent
dans l'historique de la chaîne pour toujours : l'application peut donc toujours être reconstituée.
10. En ligne de commande
zoobc-cli couvre les six types. Le premier paramètre est toujours la clé privée de l'expéditeur.
| Commande | Type |
|---|---|
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 liste les champs de chacune d'elles. Consultez le Manuel de la CLI pour l'ensemble des
commandes et le Manuel des transactions pour la structure en octets des 50 types de transaction.
11. Constantes
| Constante | Valeur | Ce qu'elle régit |
|---|---|---|
| Commission | 1 % du pot | versée à la réserve des applications, uniquement en cas d'issue décisive |
| Délai par coup | 240 blocs | ~1 heure ; réinitialisé à chaque coup accepté |
| Résolution solo | hauteur du pari + 2 | ~30 secondes |
| Plafond de bankroll | 5 % de la réserve | gain maximal d'un seul pari solo |
| Plafond multi-parties | 100 | parties par pari solo |
| Plafond de coups par règlement | 1024 | entrées par SettleApp, ≥67 octets chacune |
| Fenêtre de contestation du règlement | 240 blocs | ~1 heure pendant laquelle un final_seq plus élevé l'emporte |
| Places | 1, ou 2–4 | 1 pour le solo ; 2–4 pour le multijoueur |
| Engagement de la bataille navale | 3200 octets | exactement, sinon la création est rejetée |