docs(sec): SEC-16 — безвозвратная утечка ренты PDA-ставки с кошелька игрока
Найдено при приёмке боевого домена smart-updown.binom.pw (симптом «SOL не обновляется»). - SEC-16: при create_bet ренту PDA-аккаунта ставки (1 559 040 lamports = (96+32+128)*6960) платит БЕТТОР, а не шлюз; баланс шлюза-админа при этом не меняется (замер whose-fee.cjs). Аккаунт не закрывается никогда (в контракте 9ALs четыре инструкции, ни одной close = bettor) → рента теряется безвозвратно. В цепи 75 аккаунтов = 0.116928 SOL заперто, растёт линейно. Вердикт: на тестах терпимо (маскируется краном, 5 SOL = ~3207 раундов), перед mainnet — блокер. - TASKS.md: фича «если цена не изменилась — подождать ещё 15 с» (НЕ доделана), заметка про боевой домен на общем TS-шлюзе и лаг крана 8–13 с. - tools: доказательные скрипты (whose-fee, bet-pda-rent, faucet-latency, sol-balance-cycle, settle-math-test, backend-full-test) + README с питфоллами замера. - k8s/smart-updown-api-ingress.yaml: /api,/ws вынесены вне helm (релиз игры их сносил).
This commit is contained in:
+77
-2
@@ -23,8 +23,9 @@ kubectl -n game exec "$P" -c gateway -- sh -c "cd /app && node /app/data/probe/e
|
||||
- **Копировать только в `/app/data/`**, не в `/app` (`Permission denied`, под не root).
|
||||
- **Запускать с `cd /app`**, иначе `Cannot find module '@solana/web3.js'` (из `/tmp`/`/app/data`
|
||||
без cwd `node_modules` не виден).
|
||||
- Ставка при `exit == entry` остаётся `open` — это ожидаемо (равные цены, отдельная задача),
|
||||
а не сбой проверки.
|
||||
- Ставка при `exit == entry`: с 2026-09-14 (`SEC-9`) **возвращает залог** статусом `3` и
|
||||
финализируется — залипаний больше нет. *Открытый вопрос:* хочется вместо немедленного возврата
|
||||
**подождать ещё 15 с** и взять цену заново (см. `TASKS.md`, «ждать, если цена не изменилась»).
|
||||
|
||||
Читает `bet_time` / `expire_time` прямо из аккаунта Bet (сырые байты) — off-chain данными не
|
||||
верим. Раскладка аккаунта (Anchor, после 8-байтового дискриминатора): `amount u64 @8`,
|
||||
@@ -43,3 +44,77 @@ node tools/price-history-probe.cjs <programId> <betId>
|
||||
|
||||
⚠️ **Символ в БД — в нижнем регистре** (`solusdt`, не `SOLUSDT`). Запрос с верхним регистром
|
||||
вернёт пусто, и это легко принять за «данных нет».
|
||||
|
||||
## `whose-fee.cjs` — с ЧЬЕГО кошелька уходит SOL за раунд (доказательство `SEC-16`)
|
||||
|
||||
Мерит SOL **игрока** в трёх точках каждого раунда (до ставки → после ставки → после расчёта)
|
||||
и SOL **шлюза-админа**. Замер: игрок `5 000 000 000 → 4 998 440 960` (−**1 559 040** lamports),
|
||||
шлюз не меняется — значит ренту PDA-аккаунта ставки платит игрок.
|
||||
|
||||
```bash
|
||||
node tools/whose-fee.cjs
|
||||
```
|
||||
|
||||
## `bet-pda-rent.cjs` — сколько ренты заперто в аккаунтах ставок
|
||||
|
||||
Обходит `getProgramAccounts` (фильтр `dataSize:96`) и показывает, что у каждой ставки PDA
|
||||
держит ровно **1 559 040** lamports. Аккаунт не закрывается никогда (`close_bet` не делает
|
||||
`close = bettor`) → рента теряется безвозвратно. Второе доказательство `SEC-16`.
|
||||
|
||||
```bash
|
||||
node tools/bet-pda-rent.cjs
|
||||
```
|
||||
|
||||
## `faucet-latency.cjs` / `sol-balance-cycle.cjs` — почему кажется, что «SOL не обновился»
|
||||
|
||||
`GET /faucet/{addr}` отвечает **мгновенно**, но `getBalance` **на цепи** показывает `0` ещё
|
||||
**8–13 с** (замеры: 13309 / 13345 / 13345 мс). Игра читает баланс не через `/api/balance`, а
|
||||
через свой nginx-прокси `/solana-rpc/getBalance` → прямо с цепи → попадает в окно лага.
|
||||
Симптом «налил, а SOL не обновился» — это лаг зачисления, а не отставание шлюза.
|
||||
|
||||
```bash
|
||||
node tools/faucet-latency.cjs # лаг крана: цепь против отчёта шлюза
|
||||
node tools/sol-balance-cycle.cjs # баланс после крана: сразу 0, через ~20 с 5 SOL
|
||||
```
|
||||
|
||||
## `settle-math-test.cjs` — математика расчёта (правильный способ замера)
|
||||
|
||||
⚠️ Шлюз **кастодиальный**, поэтому дельту надо считать **от момента после lock** (фантики ушли
|
||||
в vault при ставке), а НЕ от старта раунда — иначе в дельту попадают и ставка, и выплата, и
|
||||
«математика не сходится» ложно. Замерено: выигрыш `+130 000 000` (1.3×), проигрыш `0`,
|
||||
ничья `+100 000 000` (возврат). Считать «от старта» = получить ложный провал (проверено живьём).
|
||||
|
||||
```bash
|
||||
node tools/settle-math-test.cjs
|
||||
```
|
||||
|
||||
## `backend-full-test.cjs` — контракт ручек и коды ошибок шлюза
|
||||
|
||||
Приёмка API живьём: `/state`, `/price`, `/wallet`, `/faucet`, `/balance`, `/bet`, идемпотентность
|
||||
крана, `400` на кривые данные, `409` на вторую открытую ставку, `404` на снятые ручки
|
||||
(`/close`, `/bet/{id}`), WS-события `bet_open`/`bet_closed`. Полный прогон с расчётом на цепи.
|
||||
|
||||
```bash
|
||||
node tools/backend-full-test.cjs
|
||||
```
|
||||
|
||||
⚠️ `/bet` требует **число** в `amountUnits` (строка → `400`) и `address` в теле.
|
||||
⚠️ `getTokenAccountsByOwner` принимает **2–3** параметра: commitment кладётся **внутрь третьего**
|
||||
(`cfg`), а не четвёртым — иначе `-32602 Expected from 2 to 3 parameters`.
|
||||
|
||||
## `k8s/smart-updown-api-ingress.yaml` — пути `/api`,`/ws` вне helm
|
||||
|
||||
⚠️ Чарт `fun-game-front2` рендерит **только** path `/`, поэтому любой `helm upgrade` игры
|
||||
**сносит** `/api` и `/ws` с боевого домена (домен начинает отдавать HTML игры на `/api/*`).
|
||||
Лечение — отдельный ingress `smart-updown-api` **вне helm**; front2-ingress остаётся `paths: [/]`.
|
||||
|
||||
```bash
|
||||
ssh root@192.168.76.120 "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml; \
|
||||
kubectl apply -f /root/smart-updown-api-ingress.yaml"
|
||||
# проверка после каждого релиза игры:
|
||||
kubectl -n game get ingress -o custom-columns='NAME:.metadata.name,HOST:.spec.rules[0].host,PATHS:.spec.rules[0].http.paths[*].path'
|
||||
```
|
||||
|
||||
⚠️ НЕ править paths через `kubectl patch --type=json` с `remove` по индексам: при нескольких
|
||||
путях индексы съезжают, остаётся висячий `/ws`, а последний path удалить нельзя
|
||||
(`paths: Required value`). Надёжно — собрать нужный список целиком и `kubectl replace --force`.
|
||||
|
||||
Reference in New Issue
Block a user