交易类型 24、25、26、27、28 和 39。交易主体、应用注册表、支付,以及如何亲自验证结果。依据节点源代码整理。
ZooBC 上的应用是一种规则在共识中运行的游戏或竞猜。每一步都是一笔交易,
棋盘保存在链状态中,赔付由协议完成。没有需要信任的服务器,没有
可以拒绝付款的运营方,也没有任何需要凭信任接受的结果,整个过程
任何人都可以根据链历史永久重放。
本手册是面向集成者的参考:六种交易类型、确切的交易主体结构、
包含每个应用走法编码的应用注册表、资金如何流动,以及随机性如何生成与核验。
这里的所有内容均摘录自节点源代码, include/zoobc/common/types.h,
src/transaction/app_rules.cpp,以及 src/transaction/transaction_executor.cpp,而非来自设计
笔记(设计笔记早于实际构建,部分内容与之不同)。
1. 三个类别
| 类别 | 席位 | 对手方 | 随机性 | 类型 |
|---|---|---|---|---|
| 单人对庄 | 1 | 协议的应用资金池 | 是,区块种子 | 16–21 |
| 一对一对战 | 2 | 另一位玩家 | 仅当应用需要时 | 1–8 |
| 多人派对 | 3–4 | 其他玩家 | 由区块种子生成的骰子 | 32–35 |
三者共用同一个引擎:相同的交易类型、相同的押注托管、相同的每步
截止期限,以及“一步即一笔交易”的相同规则。
seats 决定类别,并与应用类型进行核对。单人应用必须满足 seats == 1
且类型在 16–31 之间;多人应用必须满足 seats 为 2–4,且类型在该范围之外。如果
设置错误,会在进入内存池时就被拒绝,而不是等到执行时。
2. 交易类型
共六种类型。所有主体均为小端序;所有金额均以原子单位表示(除以 1e8 即为 ZBC)。
| 类型 | 名称 | 主体 |
|---|---|---|
| 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 的两个可选尾部字段
opponent 和 channel 都是可选的,包含哪一个由剩余的字节数决定,
剩余字节数从以下字段之后算起 params:
| 剩余字节数 | 含义 |
|---|---|
| 0 | 公开应用,链上对局 |
| 1 | channel (仅此项) |
| 36 | opponent (仅此项) |
| 37 | opponent ,其后为 channel |
设置 opponent 会把应用变成定向挑战,只有该地址可以加入。省略它,任何人
都可以占据该席位。channel = 1 表示这是一个状态通道应用,且仅在以下条件下有效: seats == 2.
多人应用将玩家存储在固定的 36 字节槽位中,因此每个就座地址都必须是 36 字节的规范格式(ZBC 地址或裸 32 字节公钥)。非 36 字节的账户在创建时即被拒绝。单人应用没有此限制,因为只有一位玩家。
3. 应用注册表
app_type 为单个字节。只有以下取值是已知的,其他任何值都会被拒绝。
一对一对战(2 个席位)
| 类型 | 应用 | 棋盘 / 状态 | 走法字节 |
|---|---|---|---|
| 1 | 井字棋 | 9 格 | [cell 0..8] |
| 2 | 国际象棋 | 64 格 | [from, to],完整合法性校验、将军、将死、逼和、自动升变为后 |
| 3 | Connect-4 | 42 格(7×6) | [column 0..6],重力落子,四子连线 |
| 4 | 国际跳棋 | 64 格 + 连跳锁定 | [from, to],强制吃子、连跳续走、王棋 |
| 5 | 黑白棋 | 64 格 | [cell 0..63],必须至少翻转一枚;无子可走的一方跳过;棋子最多者获胜 |
| 6 | 五子棋 | 225 格(15×15) | [x, y],五子连线 |
| 7 | 海战棋 | 6400 承诺 + 200 揭示 | 承诺-揭示,见下文 |
| 8 | 点格棋 | 24 条边 + 9 个格子 | [edge 0..23],完成一个格子可再走一步 |
单人对庄
| 类型 | 应用 | params | 赔率 |
|---|---|---|---|
| 16 | 双骰 | [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 | 老虎机 | 无 | 三个相同 15×,三个 7 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 精确总点数。对于精确总点数,
倍数为 3420 / ways(以百分之一为单位),其中 ways = 6 - |7 - total|,因此 7 点赔 5.70×,2 点或
12 点赔 34.20×。轮盘共有 37 个格子;0 为绿色,押任一颜色都算输。
多人派对(3–4 个席位)
| 类型 | 应用 | 状态 | 走法字节 |
|---|---|---|---|
| 32 | 飞行棋 | seats×4 棋子位置 + 待用骰子点数 | 两阶段:掷骰,然后选择一枚棋子 |
| 33 | Pig 骰子 | seats 得分 + 本回合累计 | [0 roll, 1 hold] |
| 34 | 竞速(蛇梯棋) | seats 位置 | [0],掷骰 |
| 35 | 简化版大富翁 | 资金、位置、地产归属、破产、阶段 | 两阶段:掷骰,然后可选择购买 |
4. 资金
押注。CreateApp 托管创建者的押注。每笔 JoinApp 都托管一份相等的押注。奖池
即为所有押注之和。stake_token_id 为 0 表示 ZBC;任何其他值则表示以对应的染色代币押注,
整个应用(奖池、抽成、赔付)都以该代币结算。
抽成。在分出胜负时,奖池的 1% 归入应用资金池,获胜者获得
其余部分。若为平局或应用被取消,所有押注都会退还,且不收取抽成。
应用资金池是一个协议账户,每种代币各有一个余额。它的来源是上述 1% 的抽成,加上
单人应用中的庄家优势。它是每一笔单人下注的对手方,因此正是它的余额
让单人游戏得以进行,你可以随时查询:
GET https://zoobc.network/api/v1/apps/pool
资金上限。单笔单人下注的最大可能赔付,不得超过资金池的 5%(按
该代币计算)。这一检查依据的是该笔下注对庄家而言的最坏情况,而非平均情况,因此任何
单次中奖都无法掏空资金池。会突破此上限的下注将被拒绝;如果一笔大额下注被拒,
通常就是这个原因。
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
玩家在用于开奖的种子产生之前两个区块就已提交,因此无法
预测或挑选种子。任何人事后都可以根据区块种子和应用 id 重新计算 r,
从而重新计算结果。结果不依赖任何链上没有的数据。
爆点游戏的爆点也取自同一个 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× 的截断即为上限,因此爆点游戏的最大可能赢额是下注额的十倍。
多局连玩。任何单人应用都可以一次下注最多玩 100 局。追加一个尾部计数
字节 N 到 params 中,位于该应用的选择字节之后。押注会在 N 局之间平均分配,
赔付则累加,因此一笔交易、一次揭示即可产生 N 个独立结果,无需
每局各等一个区块。省略 N 表示只玩一局,而单局的结果与
多局连玩功能出现之前逐位相同。N 必须在 1–100 之间,且押注必须能被均分,使每局
至少押注一个单位。
6. 回合、截止期限与结束
当最后一个席位被占满时,应用即变为活跃状态。此后每一步被接受的走法都会设置:
deadline_height = current_height + 240 (~1 hour at 15 s per block)
只有轮到的玩家、在应用处于活跃状态时、并且在截止期限
到达之前提交的走法才会被接受。应用有四种结束方式:
- 按规则分出胜负或平局。立即结算:向获胜者付款,若为平局则退还所有押注。
ResignApp. 你认输;你在奖池中的份额归对手所有。ClaimAppTimeout. 你的对手错过了 240 个区块的截止期限。你提出申领,即可赢得奖池。
申领需要你自己发起,截止期限过去并不会自动触发任何操作。
- 无人加入。始终无人接受的公开应用会被取消,押注退还。
7. 状态通道与 SettleApp
对于一对一对战应用,在链上走每一步都要消耗一笔交易和一个区块。
状态通道则将其换成一次性结算:使用 channel = 1 和 seats = 2 创建,
在链下对局并由双方签署每一步,最后将整局一次性作为 SettleApp(类型 39)提交。
链会从应用的初始状态出发,通过同一个规则
引擎按顺序重放已签名的走法,核验每个签名和每个回合,并向获胜者付款。一次结算在上链后
的 240 个区块内,可被携带更高 final_seq 的结算覆盖;一旦挑战窗口
结束,结算即告完成。
主体上强制执行三项限制,集成者必须全部遵守:
final_seq必须等于move_count.move_count不得超过 1024 个条目。move_count不得超过剩余主体实际能容纳的数量。每个条目至少 67
字节(seat(1) + move_len(2) + signature(64)),因此大于 remaining / 67 的计数
会在分配任何字节之前就被拒绝。
1024 个条目是按单步(ply)计算的限制,而不是按回合计算:一个条目即一步,因此 1024 个条目的结算可覆盖一局 512 回合的对局。有记录以来最长的国际象棋对局为 269 回合、538 步,因此这一上限大约是实际对局最长纪录的两倍。
8. 海战棋(类型 7)
海战棋是唯一需要隐藏信息的一对一对战应用,因此采用承诺-揭示机制。
棋盘为 10×10。对于 100 个格子中的每一个,玩家选择一个随机的 32 字节盐值并提交
SHA3(is_ship ‖ salt);承诺即这 100 个哈希的拼接,共 3200 字节,在创建时作为
params 传入。seats 必须为 2,且承诺必须恰好为 3200 字节,否则创建
会被拒绝。
对局交替使用两种走法形式:
- 开火
[0, cell]:你向对手棋盘上的一个格子开火。随后轮到对方揭示。 - 揭示
[1, cell, is_ship, salt(32)]:对方通过打开该格子的
承诺来证明那里有什么。由于每个格子都是单独承诺的,玩家无法在任何一个格子上撒谎。
命中或未命中都会被记录,然后由揭示方开火。
击中全部 17 个舰船格子即获胜。拒不揭示的玩家与其他走法一样受时限约束,
因此适用 ClaimAppTimeout。
手续费随大小增长。3200 字节的创建交易属于大型交易,而手续费下限会随交易大小增长。在实际运行的链上,0.1 ZBC 的手续费因过低被拒绝,约 5 ZBC 则被接受。请为创建交易预留这笔费用;开火和揭示走法体积小,费用低。
9. 读取应用状态
由节点 API 提供的五个端点,请注意这些是节点端点,而不是归档节点端点:
基础 URL 为 https://zoobc.network/api/v1。这是公共 ZooBC TestNet 网关,而不是 MainNet。
| 端点 | 返回内容 |
|---|---|
GET /api/v1/apps | 大厅;可用以下参数筛选: status=, category=solo|pvp|party, limit= |
GET /api/v1/apps/open | 任何人都可加入的公开一对一挑战 |
GET /api/v1/apps/:id | 单个应用的完整状态 |
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 支持全部六种类型。第一个参数始终是发送方的私钥。
| 命令 | 类型 |
|---|---|
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% | 任意单笔单人下注的最大赔付 |
| 多局连玩上限 | 100 | 每笔单人下注的局数 |
| 结算走法上限 | 1024 | 每个 SettleApp 的条目数,每条 ≥67 字节 |
| 结算挑战窗口 | 240 个区块 | 约 1 小时,期间更高的 final_seq 胜出 |
| 席位 | 1,或 2–4 | 1 为单人;2–4 为多人 |
| 海战棋承诺 | 3200 字节 | 必须恰好为此值,否则创建会被拒绝 |