Типы транзакций 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. Типы транзакций
Шесть типов. Все тела в порядке little-endian; все суммы в атомарных единицах (для 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
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 в ряд |
| 4 | Шашки | 64 клетки + блокировка серии взятий | [from, to], обязательное взятие, продолжение серии взятий, дамки |
| 5 | Реверси | 64 клетки | [cell 0..63], нужно перевернуть хотя бы одну фишку; сторона без хода пропускает; побеждает тот, у кого больше фишек |
| 6 | Гомоку | 225 клеток (15×15) | [x, y], 5 в ряд |
| 7 | Морской бой | 6400 на обязательство + 200 на раскрытие | commit-reveal, см. ниже |
| 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×, три семёрки 50×, любая пара 1.8× |
| 20 | Лотерея | [pick 0..99] | 90× |
| 21 | Краш | [target ×100, 2 bytes LE] | target ×, от 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 | Свинья | 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
Игрок делает ставку за два блока до появления сида, по которому она разрешается, поэтому он не может
ни предсказать, ни выбрать его. Любой может затем пересчитать 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×
Ставка выигрывает stake × target / 100, если C >= target. 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).
Блокчейн воспроизводит упорядоченные подписанные ходы от начального состояния приложения через тот же
движок правил, проверяет каждую подпись и очерёдность ходов и выплачивает выигрыш победителю. Расчёт может быть переопределён
другим расчётом с более высоким final_seq в течение 240 блоков после его включения в блок; когда это окно оспаривания
закрывается, расчёт становится окончательным.
К телу применяются три ограничения, и интегратор должен соблюдать все три:
final_seqдолжно равнятьсяmove_count.move_countне может превышать 1024 записей.move_countне может превышать того, что физически помещается в оставшуюся часть тела. Каждая запись занимает не менее 67
байтов, seat(1) + move_len(2) + signature(64), поэтому количество больше remaining / 67
отклоняется до выделения хотя бы одного байта.
Лимит в 1024 записи считается в полуходах, а не в парах ходов: одна запись соответствует одному полуходу, поэтому расчёт из 1024 записей покрывает партию в 512 ходов. Самая длинная из зафиксированных шахматных партий длилась 269 ходов, то есть 538 полуходов, так что лимит примерно вдвое превышает самую длинную партию, когда-либо сыгранную на самом деле.
8. Морской бой (тип 7)
Из всех приложений один на один только Морскому бою нужна скрытая информация, поэтому в нём используется схема commit-reveal.
Поле имеет размер 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 байтов | ровно, иначе создание отклоняется |