Новая схема работы Arweave через Turbo

This commit is contained in:
AidarKC
2026-09-25 13:19:24 +03:00
parent ef707ec217
commit 408e474130
30 changed files with 544 additions and 241 deletions
+103 -44
View File
@@ -2,96 +2,155 @@
## Цель
Каждый пользовательский блок уже на клиенте является самостоятельным подписанным ANS-104 DataItem. Сервер не переподписывает пользовательский контент: он проверяет его, хранит в PostgreSQL и объединяет готовые DataItems в стандартный ANS-104 bundle.
Каждый пользовательский блок SHiNE уже на клиенте является самостоятельным подписанным ANS-104 DataItem. Сервер проверяет и хранит **точно эти signed bytes** и может публиковать их одним из двух транспортов: через Turbo по одному DataItem либо через прямую Arweave L1-транзакцию в составе стандартного большого ANS-104 bundle.
## Child DataItem tags
Способ публикации — локальная политика конкретного сервера. Формат пользовательского блока и импорт от него не зависят.
Обязательно для тестового контура:
## User DataItem tags
Для тестового контура обязательно:
```text
App=test5590
```
Дополнительно для блоков конкретного канала:
Для блоков конкретного канала дополнительно:
```text
c=<canonical_channel_slug>
c_test5590=<canonical_channel_slug>
```
Теги входят в ANS-104 подпись пользователя.
Теги входят в ANS-104 подпись пользователя. Старый тестовый тег `c` новым кодом не создаётся и не принимается как channel tag.
## Publisher
## Publisher modes
По умолчанию цикл — раз в 15 минут.
Настройка:
```text
arweave.blocks.publish.mode=turbo | arweave | none
```
### `turbo`
```text
blocks.arweave_publish_pending=true
↓
готовые serialized DataItems
готовый signed user DataItem из blocks.block_bytes
↓
ANS-104 binary bundle
POST в Turbo как application/octet-stream
↓
обычная Arweave L1 transaction
Turbo bundling / Arweave
```
Если pending-блоков нет, транзакция не создаётся.
DataItem **не переподписывается** сервером. Его `data_item_id = SHA-256(user signature)` до и после загрузки должен оставаться тем же.
Root transaction содержит стандартные bundle tags:
Для Turbo можно задать публичный payer address напрямую:
```text
arweave.blocks.publish.turbo.paidByAddress=...
```
либо путь к серверному Arweave JWK:
```text
arweave.blocks.publish.turbo.walletJwkPath=/path/to/server-turbo-wallet.json
```
Из JWK локально вычисляется только публичный Arweave address для `x-paid-by`; приватный ключ Turbo upload endpoint не получает.
Важно: если upload уже требует оплаты, а signed DataItem принадлежит другому signer, Turbo Credits серверного кошелька используются через Credit Share Approval в пользу signer-адреса. Для маленьких DataItem, попадающих под действующий free tier Turbo, payer может не понадобиться. Код не должен рассчитывать на вечное существование free tier: HTTP `402` считается ошибкой оплаты и блок остаётся pending.
### `arweave`
Сохраняется прежний fallback:
```text
pending user DataItems
↓
standard ANS-104 binary bundle
↓
server Arweave RSA/JWK signature
↓
Arweave L1
```
Root transaction содержит только стандартные bundle tags:
```text
Bundle-Format=binary
Bundle-Version=2.0.0
Content-Type=application/octet-stream
App=test5590-batch
```
`App=test5590-batch` намеренно отличается от child `App=test5590`, чтобы discovery-запрос находил пользовательские блоки, а не root bundles.
Специальный `App=test5590-batch` больше не используется. Важны вложенные user DataItems, у которых уже есть `App=test5590`.
После успешной L1-загрузки сервер ставит child-блокам:
### `none`
Сервер принимает и хранит блоки локально, но publisher не отправляет их в Arweave/Turbo. Importer при этом может работать независимо.
## Состояние публикации в БД
После успешной публикации:
- `arweave_publish_pending=false`;
- `arweave_published_at_ms`;
- `arweave_root_tx_id`.
- заполняется `arweave_published_at_ms`.
## Importer
`arweave_root_tx_id` больше не хранится: один и тот же пользовательский DataItem может быть физически упакован разными bundler-ами, а стабильным сетевым идентификатором SHiNE является именно `data_item_id`.
Каждый сервер может независимо искать:
## Importer: только individual DataItems
Importer всегда выполняет один discovery-запрос:
```text
App=test5590
```
через GraphQL gateway с cursor pagination.
Он **не ищет root bundles** и не зависит от `publish.mode`.
Для каждого нового DataItem:
Это одинаково работает для:
1. взять `id` и `bundledIn.id`;
2. получить root bundle;
3. извлечь точные serialized bytes child DataItem по bundle index;
4. проверить `dataItemId == SHA256(signature)`;
5. проверить ANS-104 Ed25519 подпись;
6. определить пользователя по `owner`;
7. применить обычные проверки `AddBlock`;
8. записать в PostgreSQL с `arweave_publish_pending=false`.
- DataItem, отправленного через Turbo;
- DataItem, находящегося внутри большого direct-Arweave ANS-104 bundle сервера.
### Блоки могут прийти не по порядку
После того как AR.IO gateway распаковал/indexed bundle, child DataItem присутствует в GraphQL как отдельная сущность со своим `id` и собственными tags.
Discovery/import использует persistent queue `arweave_block_import_queue`. Если, например, block 102 увиден раньше block 101, block 102 остаётся `PENDING`; после появления 101 очередь повторно проигрывается.
### Получение полного signed DataItem
## Дедупликация и несколько серверов
Обычная выдача DataItem по gateway URL может представлять только payload, а SHiNE для криптографической проверки нужны полные serialized ANS-104 bytes.
Один и тот же готовый DataItem имеет один `data_item_id = SHA256(signature)`. Если несколько серверов включили его в разные root bundles, локально это всё равно один логический блок: `blocks.data_item_id` уникален.
Поэтому importer:
Импортированный из Arweave блок **не ставится обратно в publish queue**. Это предотвращает бесконечное переархивирование между серверами.
1. получает `data_item_id` через GraphQL `App=test5590`;
2. запрашивает `GET /ar-io/offsets/{data_item_id}`;
3. получает `rootTxId`, `rootOffset`, `size`;
4. делает range-read `GET /raw/{rootTxId}` ровно по этому диапазону;
5. разбирает полученные bytes как `Ans104DataItem`;
6. проверяет, что `SHA-256(signature) == data_item_id`;
7. проверяет Ed25519 ANS-104 signature;
8. определяет пользователя по `owner`;
9. импортирует через обычную логику `AddBlock` без повторной публикации.
Если GraphQL уже увидел DataItem, но gateway ещё не подготовил offsets, checkpoint не продвигается за этот height и DataItem будет повторён в следующем цикле.
## Очередь и порядок блоков
`arweave_block_import_queue` хранит:
- `data_item_id`;
- `block_height`;
- полный `raw_data_item`;
- status/error/timestamps.
`root_tx_id` очереди больше не нужен.
Если block N+1 увиден раньше N, он остаётся `PENDING`; после появления предыдущего блока очередь повторно проигрывается.
## Дедупликация
`blocks.data_item_id` уникален. Один signed DataItem остаётся одним логическим SHiNE-блоком независимо от того, сколько серверов или bundler-ов физически включили его в Arweave.
Импортированный блок записывается через `AddBlock` с отключённой повторной публикацией, поэтому серверы не создают цикл переархивирования.
## Локальное хранение
Пользовательские blockchain-файлы на диске больше не используются. Полный serialized DataItem находится в `blocks.block_bytes` PostgreSQL.
## Настройки
См. `application.properties` и `CODEX_APPLY_ANS104_TEST5590_PATCH.md`.
## Что намеренно не входит в этот патч
Remote/homeserver signing path, связанный с внешним homeserver/ESP32 signer, не мигрируется этим патчем. Каталог `ESP32/` не изменяется. До отдельной миграции новый Frame v1/ANS-104 production path рассчитан на клиент, у которого локально доступен blockchain Ed25519 key.
Полный serialized signed DataItem хранится в `blocks.block_bytes` PostgreSQL. Пользовательские `.bch`-файлы не являются источником истины.