Песочница
Для разработчиков площадки: тестовый ключ, свой журнал и первое событие за 15 минут. Ключ приходит письмом после короткой проверки заявки.
Песочница нужна, чтобы подключить площадку до боевого ключа: создать акцию, отправить записи, получить квитанции и итог, открыть страницу проверки. Протокол и объекты там те же, что в бою. Другие у песочницы ключи, журнал и сроки.
API песочницы: https://api.sandbox.lchv.ru, страница проверки: https://verify.sandbox.lchv.ru. Каждый ответ API песочницы несёт заголовок X-Sandbox: 1, по нему ваш код отличит песочницу от боевого сервиса.
Чем песочница отличается от боевого сервиса
- Свой журнал. У песочницы свой общий журнал со своей цепочкой и отметками и свой идентификатор сервиса в условиях (
service, на стендеnotarius-stage-sandbox). Ключи подписи тоже свои, поэтому квитанция из песочницы в боевом сервисе не проверится, и наоборот. - Свой Rekor, не публичный. Отметки песочницы уходят в экземпляр Rekor, который держим мы. В публичный
rekor.sigstore.devпесочница не пишет никогда: тесты не засоряют открытый журнал. Ключ этого Rekor знает только сборка страницы проверки песочницы. Боевая страница его не знает, и "Совпало" для розыгрыша песочницы не покажет. - drand настоящий. Раунд, его подпись и формула победителя те же, что в бою.
- Окно приёма от одной минуты, итог через минуту после закрытия. Окно короче 60 секунд песочница не примет. Если в черновике нет
margin_sиreveal_after_round_s, она ставит 60 и 120 секунд вместо боевых 300 и 600 у акций: раунд drand выходит через минуту после закрытия, итог сразу за ним. Заданные вами значения остаются как есть. - Данные живут 30 дней. Акция, в которой 30 дней ничего не происходило, стирается целиком: записи, квитанции в базе, итог, условия и файлы на странице проверки. В общем журнале остаются только листья с отпечатками и временем, на них держится цепочка других розыгрышей.
- Нет знака и реестра. Акция песочницы не получает знак и не попадает в реестр площадок. Сверху на странице проверки стоит плашка "Песочница".
- Канала пока нет. Открытый канал песочницы ещё не заведён, посты об отметках не публикуются. На проверку это не влияет: время отметки засчитывается только по Rekor.
Лимиты
Кроме open_at, этих ограничений в бою нет: они берегут песочницу, которую делят все площадки.
| Что | В песочнице | Ответ сверх лимита |
|---|---|---|
Окно приёма, от open_at до close_at | не короче 60 с | 422 window |
open_at от момента запроса | не ближе 60 с, как в бою | 422 open_at |
| Новых розыгрышей на площадку за сутки, черновики тоже | 20 | 429 rate_limited |
| Записей на площадку, все ключи вместе | 5 в секунду, разово до 20 подряд | 429 rate_limited |
На 429 запрос стоит повторить чуть позже. Остальные коды ошибок те же, что в бою (docs/API.md, раздел "Ошибки").
Первое событие за 15 минут
Нужны bash, curl, openssl и тестовый ключ: идентификатор k_... и секрет из 64 знаков hex. Ключ выдаёт оператор сервиса, секрет показывается один раз.
1. Подпись запроса
Каждый запрос площадки подписан HMAC-SHA256 секретом ключа (docs/API.md, раздел "Ключ и подпись запроса"). Функция call ниже делает подпись и сам запрос, at пишет время в формате протокола.
API=https://api.sandbox.lchv.ru
KEY_ID=k_...
SECRET=<64 знака hex>
# call МЕТОД ПУТЬ [ТЕЛО [ещё аргументы curl]]; без тела, но с аргументами curl тело пишется как ''
call() {
local method=$1 path=$2 body=${3:-}
shift $(( $# < 3 ? $# : 3 ))
local ts nonce sum sig
ts=$(date +%s)
nonce=$(openssl rand -hex 16)
sum=$(printf %s "$body" | openssl dgst -sha256 -r | cut -d' ' -f1)
sig=$(printf '%s\n%s\n%s\n%s\n%s' "$method" "$path" "$ts" "$nonce" "$sum" \
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$SECRET" -r | cut -d' ' -f1)
local args=(-sS -X "$method" "$API$path" -H "X-Key-Id: $KEY_ID" -H "X-Timestamp: $ts" -H "X-Nonce: $nonce" -H "X-Signature: $sig")
[ -z "$body" ] || args+=(-H 'Content-Type: application/json' --data-binary "$body")
curl "${args[@]}" "$@"
echo
}
# Время через N секунд от сейчас, UTC с миллисекундами; GNU date и date на маке.
at() {
local t=$(( $(date +%s) + $1 ))
date -u -d "@$t" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date -u -r "$t" +%Y-%m-%dT%H:%M:%S.000Z
}Пустое тело подписывается как SHA-256 от нуля байт, printf %s "" это и даёт. Часы машины должны идти верно: запрос, который разошёлся с часами сервиса больше чем на 300 секунд, получит 401 replay.
Проверка: call GET /v1/draws/no-such-draw '' -i отвечает 404 unknown_draw с заголовком x-sandbox: 1. Если пришёл 401, подпись не сошлась.
2. Акция: окно три минуты, один победитель
DRAW=first-event-$(date +%s)
OPEN=$(at 90)
CLOSE=$(at 270)
RULES=$(printf 'Правила акции для проверки подключения.' | base64 | tr -d '\n')
BODY=$(cat <<EOF
{"kind":"bookmaker_promo","draw":"$DRAW","organizer":"Ваша компания","title":"Проверка подключения",
"rules_ack":["promo-v1"],"open_at":"$OPEN","close_at":"$CLOSE",
"formula":{"winners":1,"alternates":0,"one_prize_per_subject":true},"prize":{"text":"Футболка"},
"participation_condition":"free","rules_url":"https://example.com/rules",
"rules_document_base64":"$RULES","rules_media_type":"text/plain","prize_delivery":"В личном кабинете"}
EOF
)
call POST /v1/draws "$BODY"Для бренда вид brand_promo, условие участия free или purchase. Документ правил бывает PDF, обычным текстом или Markdown.
Ответ 201 с черновиком и условиями целиком. Что в нём смотреть:
conditions_hash, отпечаток условий: по нему проверяющий видит, что условия не менялись после фиксации;conditions.commitment, отпечаток секретного числа, которое сервис раскроет после раунда;conditions.roundиconditions.round_time, номер и время раунда drand, от которого зависит победитель;conditions.margin_sравно 60,conditions.reveal_deadlineчерез 120 секунд после раунда: значения песочницы;conditions.service, идентификатор сервиса песочницы.
Черновик видит только ваша площадка. До фиксации его можно поправить запросом PUT /v1/draws/<draw> с тем же телом.
3. Фиксация
call POST /v1/draws/$DRAW/commitСервис пишет лист фиксации в журнал песочницы, затем отмечает его в Rekor песочницы. Ответ ждёт лист до 5 секунд: 200 с "state":"committed", а если не дождался, 202 с "state":"committing", тогда состояние спросите позже.
В ответе commit_index, номер листа в общем журнале, и files, ссылка на страницу проверки акции https://verify.sandbox.lchv.ru/d/<draw>/. Ссылку можно открыть сразу, полной страница станет после итога.
Приём откроется в open_at, через полторы минуты после создания:
call GET /v1/draws/$DRAWТам "state":"open", "entries":0 и commit_anchored_at, время отметки фиксации в Rekor. Оно всегда раньше open_at: без отметки приём не открывается вовсе.
4. Три записи и повтор
for i in 1 2 3; do
call POST /v1/draws/$DRAW/entries "{\"platform\":\"site\",\"user_ref\":\"player-$i\"}" -H "Idempotency-Key: event-$i"
doneplatform это короткое имя площадки латиницей. user_ref это ваш внутренний идентификатор игрока, без имени, почты и телефона: сервис хранит только его производную. Idempotency-Key это ключ события на вашей стороне, он обязателен.
Каждый ответ 201 с квитанцией. В ней leaf.seq, номер записи в журнале акции с нуля, entry_id, код квитанции, и подпись сервиса в sigs. Теперь первое событие ещё раз:
call POST /v1/draws/$DRAW/entries '{"platform":"site","user_ref":"player-1"}' -H 'Idempotency-Key: event-1'Ответ 200 и та же квитанция: тот же номер записи, тот же код, та же подпись. Тот же ключ с другим телом получит 409 idempotency_conflict.
5. Квитанция у нас
Игрок открывает квитанцию на странице проверки по ссылке https://verify.sandbox.lchv.ru/r/<draw>/<seq>#<entry_id>: код стоит после # и на сервер не уходит. Квитанцию по коду можно получить и запросом, подпись для него не нужна:
curl -sS -X POST "$API/v1/draws/$DRAW/receipts/lookup" -H 'Content-Type: application/json' \
--data-binary '{"seq":0,"entry_id":"<entry_id первой квитанции>"}'Ответ: та же квитанция, а после закрытия ещё доказательство включения записи (inclusion) и чекпоинт журнала акции, до которого оно ведёт.
6. Закрытие и итог
Досрочного закрытия у акций нет, приём закроется сам в close_at. Примерно через минуту:
curl -sS -i "$API/v1/draws/$DRAW/result"202 значит "ещё рано", 200 отдаёт подписанный итог с номерами записей победителей. 410 значит, что акция не состоялась по правилам, например её отменили.
Теперь откройте ссылку files из шага 3. Страница сама скачает файлы акции, пересчитает итог в браузере и напишет "Совпало". Сверху будет плашка "Песочница".
7. Что дальше
- Повторяйте
Idempotency-Keyпри повторной отправке того же события: с новым ключом это уже другая запись. - Показывайте квитанцию игроку и давайте ссылку на неё у нас, как в шаге 5.
- Когда сценарий проходит, площадка подаёт заявку на боевой ключ. Боевой API живёт на
https://api.lchv.ru, там нет заголовкаX-Sandboxи действуют боевые сроки и лимиты.