トランザクションタイプ24、25、26、27、28、39。ボディ、アプリレジストリ、支払い、そして結果を自分で検証する方法。ノードのソースから書き起こしたものです。
ZooBCにおけるアプリとは、ルールがコンセンサス内で実行されるゲームや賭けのことです。すべての手はトランザクションであり、
盤面はチェーンの状態に保持され、配当はプロトコルが支払います。信頼しなければならないサーバーも、
支払いを拒否できる運営者も、誰かが鵜呑みにしなければならない結果も存在しません。すべては
チェーンの履歴から誰でも、いつまでも再現できます。
本マニュアルは統合開発者向けのリファレンスです。6種類のトランザクションタイプ、その正確なボディ、
各アプリの手のエンコーディングを含むアプリレジストリ、お金の動き方、乱数の導出と検証の方法を扱います。
ここに記載した内容はすべて、次のノードのソースコードから転記したものです: include/zoobc/common/types.h,
src/transaction/app_rules.cpp、src/transaction/transaction_executor.cpp。設計メモに基づくものではありません。
設計メモは実装より前に書かれたもので、ところどころ実装と異なります。
1. 3つのカテゴリ
| カテゴリ | 席数 | 相手方 | 乱数 | タイプ |
|---|---|---|---|---|
| ソロ対ハウス | 1 | プロトコルのアプリプール | あり(ブロックシード) | 16–21 |
| 1対1 | 2 | 別のプレイヤー | アプリが使う場合のみ | 1–8 |
| パーティー | 3–4 | ほかのプレイヤー | ブロックシードによるサイコロ | 32–35 |
3つとも同じエンジンを共有しています。同じトランザクションタイプ、同じ賭け金のエスクロー、同じ1手ごとの
期限、そして「1手=1トランザクション」という同じルールです。
seats でカテゴリが決まり、アプリタイプと照合されます。ソロアプリは seats == 1
かつタイプが16〜31でなければなりません。マルチプレイヤーアプリはseatsが2〜4、かつタイプがその範囲外でなければなりません。この
指定を誤ると、実行時ではなくメンプールへの受け入れ時点で拒否されます。
2. トランザクションタイプ
6種類です。ボディはすべてリトルエンディアンで、金額はすべてアトミック単位です(ZBCに換算するには1e8で割ります)。
| タイプ | 名前 | ボディ |
|---|---|---|
| 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)]* |
CreateAppの末尾にある2つの任意フィールド
opponent とchannelはどちらも任意です。どちらが含まれるかは、
次のフィールドより後ろに残るバイト数で決まります: params:
| 残りバイト数 | 意味 |
|---|---|
| 0 | オープンなアプリ、オンチェーンでプレイ |
| 1 | channel のみ |
| 36 | opponent のみ |
| 37 | opponent 、続いて channel |
opponentを指定するとアプリは直接対戦になり、そのアドレスだけが参加できます。省略すると誰でも
席に着けます。channel = 1はステートチャネルアプリであることを示し、有効なのは次の場合のみです: seats == 2.
マルチプレイヤーアプリはプレイヤーを固定長36バイトのスロットに格納するため、着席するアドレスはすべて36バイトの正規形式(ZBCアドレス、または32バイトの公開鍵そのもの)でなければなりません。36バイトでないアカウントは作成時に拒否されます。ソロアプリにはこの制限はありません。プレイヤーが1人しかいないからです。
3. アプリレジストリ
app_type は1バイトです。既知の値は以下のみで、それ以外はすべて拒否されます。
1対1(2席)
| タイプ | アプリ | 盤面 / 状態 | 手のバイト列 |
|---|---|---|---|
| 1 | 三目並べ | 9マス | [cell 0..8] |
| 2 | チェス | 64マス | [from, to]、完全な合法手判定、チェック、チェックメイト、ステイルメイト、クイーンへの自動昇格 |
| 3 | Connect-4 | 42マス(7×6) | [column 0..6]、重力あり、4つ並べ |
| 4 | チェッカー | 64マス + 連続ジャンプのロック | [from, to]、強制取り、連続ジャンプ、キング |
| 5 | リバーシ | 64マス | [cell 0..63]、最低1枚は裏返す必要あり。打てる手がない側はパス。石の多い側が勝ち |
| 6 | 五目並べ | 225マス(15×15) | [x, y]、5つ並べ |
| 7 | バトルシップ | コミットメント6400 + 公開200 | コミット・リビール方式(後述) |
| 8 | ドット・アンド・ボックス | 24辺 + 9ボックス | [edge 0..23]、ボックスを完成させるともう1手 |
ソロ対ハウス
| タイプ | アプリ | params | 配当 |
|---|---|---|---|
| 16 | 2個のサイコロ | [bet_type 0..3, total] | 7未満 / 7超 2.28×、ラッキー7 5.7×、合計ぴったり 3420/ways % |
| 17 | コイントス | [choice 0/1] | 1.98× |
| 18 | ルーレット | [bet_type, value] | 単一の数字 36×、色 2× |
| 19 | スロット | なし | 3つ揃い 15×、7が3つ 50×、任意のペア 1.8× |
| 20 | くじ | [pick 0..99] | 90× |
| 21 | クラッシュ | [target ×100, 2 bytes LE] | 目標倍率 ×、1.01×〜10.00× |
サイコロの賭けの種類は、0が7未満、1がラッキー7、2が7超、3が合計ぴったりです。合計ぴったりの場合、
倍率は100分の1単位で3420 / ways(ここでways = 6 - |7 - total|)となるため、7なら5.70×、2または
12なら34.20×が支払われます。ルーレットのポケットは37個で、0は緑のため、どちらの色に賭けても負けとなります。
パーティー(3〜4席)
| タイプ | アプリ | 状態 | 手のバイト列 |
|---|---|---|---|
| 32 | ルド | seats×4 個のコマ位置 + 保留中の出目 | 2段階:サイコロを振ってから、コマを選ぶ |
| 33 | ピッグ | seats 人分のスコア + 手番の合計 | [0 roll, 1 hold] |
| 34 | レース(蛇と梯子) | seats 人分の位置 | [0]、サイコロを振る |
| 35 | モノポリー・ライト | 所持金、位置、所有権、破産、フェーズ | 2段階:サイコロを振ってから、任意で購入 |
4. お金の流れ
賭け金。CreateAppは作成者の賭け金をエスクローします。JoinAppのたびに同額の賭け金がエスクローされます。ポットは
その合計です。stake_token_idが0ならZBCを意味し、それ以外の値ならそのカラードトークンを賭けることになり、
アプリ全体(ポット、レーキ、配当)がそのトークンで精算されます。
レーキ。勝敗が決した場合、ポットの1%がアプリプールに入り、勝者が
残りを受け取ります。引き分けやキャンセルされたアプリでは、すべての賭け金が返金され、レーキは徴収されません。
アプリプールは、トークンごとに1つの残高を持つプロトコルのアカウントです。この1%のレーキと、
ソロアプリのハウスエッジによって増えていきます。すべてのソロの賭けの相手方であるため、その残高があってこそ
ソロプレイが成り立ちます。残高はいつでも確認できます:
GET https://zoobc.network/api/v1/apps/pool
バンクロール上限。1回のソロの賭けで起こり得る最大配当は、そのトークンのプールの5%を
超えることができません。この判定は平均ではなく、その賭けにおけるハウス側の最悪のケースに対して行われるため、
1回の当たりでプールが枯渇することはありません。上限を超える賭けは拒否されます。大きな賭けが拒否された場合、
たいていはこれが理由です。
5. 乱数とその検証方法
ソロアプリの結果はブロックシードによって決まります。ZooBCにおけるブロックシードは次のとおりです:
block_seed = blocksmith_signature(SHA3(previous_block.block_seed))。決定論的で、
ブロックスミスの公開鍵を使って誰でも検証でき、ブロックが存在するまでは知りようがありません。
ブロック高Hで行われた賭けの結果が確定するのは、ブロック高 H + 2:
r = SHA3-256( block_seed[H+2] ‖ app_id ) → the low 8 bytes, little-endian, as a uint64
プレイヤーは、判定に使われるシードが生成される2ブロック前に賭けを確定させるため、
シードを予測することも選ぶこともできません。rは後からブロックシードとアプリIDを使って誰でも再計算でき、
したがって結果も再計算できます。結果のどの部分も、チェーンが保持していないデータには依存しません。
クラッシュのクラッシュ地点の導出に使われるのも、同じ r:
u = r mod 1e6
C = 99_000_000 / (1_000_000 - u) clamped to [100, 1000], i.e. 1.00× .. 10.00×
C >= targetのとき、賭けはstake × target / 100を獲得します。分子の99はハウス
エッジ(1%)です。10.00×の制限が上限なので、クラッシュで得られる最大の配当は賭け金の10倍です。
マルチプレイ。どのソロアプリも、1回の賭けで最大100回プレイできます。末尾に回数を表す
バイトNを、そのアプリの選択バイトの後ろに付けてparamsに追加します。賭け金はN回のプレイに均等に分割され、
配当は合算されます。そのため、1回のトランザクションと1回の公開でN個の独立した結果が得られ、
1回ごとにブロックを待つ必要はありません。Nを省略すると1回のプレイとなり、その1回のプレイは
マルチプレイ導入前とビット単位で同一です。Nは1〜100でなければならず、賭け金は各プレイが
最低1単位を賭けられるように割り切れる必要があります。
6. 手番、期限、終了
アプリは最後の席が埋まった時点でアクティブになります。それ以降、手が受理されるたびに次の値が設定されます:
deadline_height = current_height + 240 (~1 hour at 15 s per block)
手が受理されるのは、手番のプレイヤーからのもので、アプリがアクティブな間であり、かつ
期限が過ぎる前に限られます。アプリの終わり方は4通りあります:
- ルール上の勝利または引き分け。即座に精算され、勝者に支払われます。引き分けの場合はすべての賭け金が返金されます。
ResignApp. あなたの投了となり、ポットのあなたの取り分は相手に渡ります。ClaimAppTimeout. 相手が240ブロックの期限を過ぎました。あなたが請求すれば、ポットを獲得できます。
請求はあなた自身が行う必要があります。期限が過ぎたというだけで自動的に何かが起こることはありません。
- 誰も参加しない。誰にも受けられなかったオープンなアプリはキャンセルされ、賭け金は返金されます。
7. ステートチャネルとSettleApp
1対1のアプリでは、すべての手をオンチェーンで指すと、1手ごとにトランザクション1件とブロック1つ分のコストがかかります。
ステートチャネルを使えば、これを1回の精算に置き換えられます。channel = 1とseats = 2で作成し、
双方がすべての手に署名しながらオフチェーンでプレイし、最後にゲーム全体をSettleApp(タイプ39)として1回だけ送信します。
チェーンは、署名済みの手を順番どおりに、アプリの初期状態から同じルール
エンジンで再生し、各署名と各手番を検証したうえで勝者に支払います。精算は、チェーンに取り込まれてから
240ブロックの間、より大きいfinal_seqを持つ別の精算によって上書きされる可能性があります。このチャレンジ期間が
過ぎると、精算が確定します。
ボディには3つの制限が課されており、統合開発者は3つすべてを守る必要があります:
final_seqは、次の値と等しくなければなりません:move_count.move_countは1024エントリを超えてはなりません。move_countは、残りのボディに物理的に収まる数を超えてはなりません。各エントリは最低67
バイト(seat(1) + move_len(2) + signature(64))なので、remaining / 67より大きい数は
1バイトも割り当てられる前に拒否されます。
1024エントリという制限は、2手1組ではなく片側の1手(プライ)単位の上限です。1エントリが1プライなので、1024エントリの精算で512手のゲームをカバーできます。記録に残る最長のチェスの対局は269手(538プライ)だったので、この上限は実際に指された最長の対局のおよそ2倍にあたります。
8. バトルシップ(タイプ7)
バトルシップは隠された情報を必要とする唯一の1対1アプリであるため、コミット・リビール方式を使います。
盤面は10×10です。プレイヤーは100マスそれぞれについてランダムな32バイトのソルトを選び、次の値をコミットします:
SHA3(is_ship ‖ salt)。コミットメントはこれら100個のハッシュを連結した3200バイトのデータで、作成時に
params として渡します。seatsは2でなければならず、コミットメントがちょうど3200バイトでなければ作成は
拒否されます。
プレイでは2種類の手を交互に使います:
- 砲撃
[0, cell]:相手の盤面の1マスを撃ちます。手番は相手に移り、相手が公開を行います。 - 公開
[1, cell, is_ship, salt(32)]:相手はそのマスの
コミットメントを開いて、そこに何があったかを証明します。各マスは個別にコミットされているため、どのマスについても嘘はつけません。
命中か外れかが記録され、次は公開した側が砲撃します。
17マスの艦船をすべて撃てば勝ちです。公開しないプレイヤーにも他の手と同様に期限が適用されるため、
ClaimAppTimeoutを使えます。
手数料はサイズに比例します。3200バイトの作成は大きなトランザクションであり、手数料の下限はトランザクションのサイズに応じて上がります。稼働中のチェーンでは、0.1 ZBCの手数料は低すぎるとして拒否され、約5 ZBCで受理されました。作成時の手数料は見込んでおいてください。砲撃と公開の手は小さく、安価です。
9. アプリ状態の読み取り
ノードAPIが提供する5つのエンドポイントです。これらはアーカイブノードではなく、通常のノードのエンドポイントである点に注意してください:
ベースURLはhttps://zoobc.network/api/v1です。これはMainNetではなく、ZooBCの公開TestNetゲートウェイです。
| エンドポイント | 返す内容 |
|---|---|
GET /api/v1/apps | ロビー。絞り込み条件: status=, category=solo|pvp|party, limit= |
GET /api/v1/apps/open | 誰でも参加できるオープンな1対1の挑戦 |
GET /api/v1/apps/:id | 1つのアプリの完全な状態 |
GET /api/v1/apps/pool | トークンごとのアプリプール残高 |
GET /api/v1/apps/stats | リアルタイムの集計:合計、オープン、アクティブ、終了、プレイヤー数、賭けられているポット |
アプリの行には、id、app_type、status(0はオープン、1はアクティブ、2は終了、3はキャンセル)、 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 および channel.
state_blobは導出された現在の盤面で、ノードがブロックごとに履歴を再生しなくて済むように保持されています。
これは利便性のためのものであり、記録ではありません。記録とは手のトランザクションであり、クライアントは監査やアニメーションのために
それらを再生できます。終了したアプリの行は猶予期間の後に削除されますが、その手は
チェーンの履歴に永久に残るため、アプリはいつでも再構築できます。
10. コマンドラインから
zoobc-cli は6種類すべてに対応しています。最初のパラメーターは常に送信者の秘密鍵です。
| コマンド | タイプ |
|---|---|
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 で、いずれのコマンドについてもフィールドを一覧表示できます。全コマンドについてはCLIマニュアルを、
全50種類のトランザクションのバイトレイアウトについてはトランザクションマニュアルを参照してください。
11. 定数
| 定数 | 値 | 対象 |
|---|---|---|
| レーキ | ポットの1% | アプリプールへ。勝敗が決した場合のみ |
| 手の期限 | 240ブロック | 約1時間。手が受理されるたびにリセット |
| ソロの結果確定 | 賭けのブロック高 + 2 | 約30秒 |
| バンクロール上限 | プールの5% | 1回のソロの賭けの最大配当 |
| マルチプレイ上限 | 100 | ソロの賭け1回あたりのプレイ数 |
| 精算エントリ上限 | 1024 | SettleApp1件あたりのエントリ数。各67バイト以上 |
| 精算のチャレンジ期間 | 240ブロック | 約1時間。この間はより大きいfinal_seqが優先 |
| 席数 | 1、または2〜4 | 1はソロ、2〜4はマルチプレイヤー |
| バトルシップのコミットメント | 3200バイト | ちょうどこのサイズでなければ作成は拒否 |