From 1a8731f4791b6c53f366e9214c852275fd2116a0 Mon Sep 17 00:00:00 2001 From: vsecoder Date: Sat, 18 Apr 2026 22:06:06 +0300 Subject: [PATCH] docs: update README + api docs + architecture for v2.0.0 feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README - Mention social feed in the one-line description and feature bullets - Add relay + feed endpoint tables to the API overview (was previously empty on messaging) - List media/ package in the repo structure docs/api/ - New docs/api/feed.md: full reference for /feed/publish, fetch, stats, view, author, timeline, trending, foryou, hashtag; all on-chain CREATE_POST / DELETE_POST / FOLLOW / LIKE payloads; fee economics; server-side scrubbing contract. - docs/api/relay.md rewritten: /relay/broadcast is now the primary E2E path with a complete envelope schema; /relay/send kept but flagged ⚠ NOT E2E; DELETE /relay/inbox/{id} documented with the new Ed25519 signed-auth body. - docs/api/README.md index: added feed.md row. docs/architecture.md - L2 Transport layer description updated to include the feed mailbox alongside the 1:1 relay mailbox. - New "Социальная лента (v2.0.0)" section right after the 1:1 message flow: ASCII diagram of publish + on-chain commit + timeline fetch, economic summary, metadata-scrub summary. docs/node/README.md - Removed stale chan:/chan-member: keys from the BadgerDB schema reference; replaced with the v2.0.0 feed keys (post:, postbyauthor:, follow:, followin:, like:, likecount:). docs/update-system.md - Example features[] array updated to match the actual node output (channels_v1 removed, feed_v2 / media_scrub / relay_broadcast added). Node feature flags - api_well_known_version.go: dropped channels_v1 tag (the /api/channels/:id endpoint was removed in the feed refactor); added feed_v2, media_scrub, relay_broadcast so clients can feature-detect the v2.0.0 surface. - Comment example updated channels_v2/v1 → feed_v3/v2. Client - CLIENT_REQUIRED_FEATURES expanded to include the v2.0.0 feature flags the client now depends on (feed_v2, media_scrub, relay_broadcast); checkNodeVersion() will flag older nodes as unsupported and surface an upgrade prompt. All 7 Go test packages green; tsc --noEmit clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 38 ++++- client-app/lib/api.ts | 3 + docs/api/README.md | 3 +- docs/api/feed.md | 279 +++++++++++++++++++++++++++++++++ docs/api/relay.md | 121 ++++++++++---- docs/architecture.md | 56 ++++++- docs/node/README.md | 8 +- docs/update-system.md | 8 +- node/api_well_known_version.go | 8 +- 9 files changed, 479 insertions(+), 45 deletions(-) create mode 100644 docs/api/feed.md diff --git a/README.md b/README.md index 6f6271d..bf35c7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DChain -Блокчейн-стек для децентрализованного мессенджера: +Блокчейн-стек для децентрализованного мессенджера + социальной ленты: - **PBFT** консенсус с multi-sig validator governance и equivocation slashing - **Native Go контракты** рядом с WASM (wazero) — нулевая задержка для @@ -8,6 +8,12 @@ - **WebSocket push API** — клиент не опрашивает, все события прилетают на соединение - **E2E-шифрованный relay mailbox** на libp2p gossipsub с TTL live-detection + (1:1 чаты через NaCl box; посты в ленте — plaintext-публичные) +- **Социальная лента v2.0.0** (заменила каналы): публичные посты + с оплатой за размер (автор платит, хостящая релей-нода получает); + on-chain граф подписок + лайки; off-chain просмотры + хэштеги; + мандаторный server-side scrubber метаданных (EXIF/GPS-стрип + FFmpeg + sidecar для видео); share-to-chat c embedded post-карточкой - **Система обновлений:** build-time версия → `/api/well-known-version`, peer-version gossip, `/api/update-check` против Gitea releases, `update.sh` с semver guard @@ -142,7 +148,8 @@ sudo systemctl enable --now dchain-update.timer | `node/` | HTTP + WS API, SSE, metrics, access control | | `node/version/` | Build-time version metadata (ldflags-инжектимый) | | `vm/` | wazero runtime для WASM-контрактов + gas model | -| `relay/` | E2E mailbox с NaCl-envelopes | +| `relay/` | E2E mailbox (1:1 envelopes) + public feed-mailbox (post bodies, view counter, hashtag index) | +| `media/` | Server-side metadata scrubber (EXIF strip + FFmpeg sidecar client) | | `identity/` | Ed25519 + X25519 keypair, tx signing | | `economy/` | Fee model, rewards | | `wallet/` | Optional payout wallet (отдельный ключ) | @@ -179,6 +186,33 @@ sudo systemctl enable --now dchain-update.timer Scoped WS-топики (`addr:`, `inbox:`, `typing:`) требуют auth через Ed25519-nonce; публичные (`blocks`, `tx`, `contract_log`) — без. +### Relay (E2E messaging) +| Endpoint | Описание | +|----------|----------| +| `POST /relay/broadcast` | Опубликовать pre-sealed envelope (E2E-путь, рекомендован) | +| `GET /relay/inbox?pub=` | Прочитать входящие конверты | +| `DELETE /relay/inbox/{id}` | Удалить envelope (требует Ed25519-подписи владельца) | + +Детали — [`docs/api/relay.md`](docs/api/relay.md). `/relay/send` оставлен +для backward-compat, но ломает E2E (nod-релей запечатывает своим ключом) +и помечен как non-recommended. + +### Social feed (v2.0.0) +| Endpoint | Описание | +|----------|----------| +| `POST /feed/publish` | Загрузить тело поста + EXIF-скраб + вернуть fee | +| `GET /feed/post/{id}` | Тело поста | +| `GET /feed/post/{id}/attachment` | Сырые байты картинки/видео (cache'able) | +| `GET /feed/post/{id}/stats?me=` | `{views, likes, liked_by_me?}` | +| `POST /feed/post/{id}/view` | Бамп off-chain счётчика просмотров | +| `GET /feed/author/{pub}?before=&limit=N` | Посты автора (пагинация `before`) | +| `GET /feed/timeline?follower=&before=&limit=N` | Merged лента подписок | +| `GET /feed/trending?window=24&limit=N` | Топ по `likes × 3 + views` за окно | +| `GET /feed/foryou?pub=&limit=N` | Рекомендации (неподписанные авторы) | +| `GET /feed/hashtag/{tag}?limit=N` | Посты по хэштегу | + +Детали + спецификация — [`docs/api/feed.md`](docs/api/feed.md). + ### Docs / UI - `GET /swagger` — **Swagger UI** (рендерится через swagger-ui-dist). - `GET /swagger/openapi.json` — сырая OpenAPI 3.0 спека. diff --git a/client-app/lib/api.ts b/client-app/lib/api.ts index 146e4fa..d72b0aa 100644 --- a/client-app/lib/api.ts +++ b/client-app/lib/api.ts @@ -480,8 +480,11 @@ export const CLIENT_PROTOCOL_VERSION = 1; */ export const CLIENT_REQUIRED_FEATURES = [ 'chain_id', + 'feed_v2', // social feed (v2.0.0) — PostCard, timeline, forYou 'identity_registry', + 'media_scrub', // server-side EXIF strip — we rely on this for privacy 'onboarding_api', + 'relay_broadcast', // /relay/broadcast for E2E envelopes (not /relay/send) 'relay_mailbox', 'ws_submit_tx', ]; diff --git a/docs/api/README.md b/docs/api/README.md index c967fd2..23af47c 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -14,7 +14,8 @@ Swagger UI на `/swagger`. Эти два эндпоинта можно выкл |---------|----------| | [chain.md](chain.md) | Блоки, транзакции, балансы, адреса, netstats, validators | | [contracts.md](contracts.md) | Деплой, вызов, state, логи контрактов | -| [relay.md](relay.md) | Relay-mailbox: отправка/приём encrypted envelopes | +| [relay.md](relay.md) | Relay-mailbox: 1:1 E2E-шифрованные envelopes | +| [feed.md](feed.md) | Социальная лента: публикация постов, лайки, подписки, рекомендации, хэштеги (v2.0.0) | ## Discovery / metadata (always on) diff --git a/docs/api/feed.md b/docs/api/feed.md new file mode 100644 index 0000000..23530b9 --- /dev/null +++ b/docs/api/feed.md @@ -0,0 +1,279 @@ +# Feed API (v2.0.0) + +REST API для социальной ленты: публикация постов, лента подписок, +рекомендации, трендинг, хэштеги. Введено в **v2.0.0**, заменило +channel-модель. + +## Архитектура + +Лента устроена гибридно — **метаданные on-chain**, **тела постов +off-chain** в relay feed-mailbox: + +``` +┌─────────────────────┐ ┌──────────────────────────┐ +│ on-chain state │ │ feed mailbox (BadgerDB)│ +│ │ │ │ +│ post: → │ │ feedpost: → │ +│ {author, size, │ POST │ {content, attachment, │ +│ content_hash, │ /feed/publish │ hashtags, type, ts} │ +│ hosting_relay, │───────────────────►│ │ +│ reply_to?, │ │ feedview: → │ +│ deleted?} │ │ uint64 (counter) │ +│ │ │ │ +│ follow:: │ │ feedtag::: │ +│ like::│ │ (hashtag index) │ +│ likecount: │ │ │ +└─────────────────────┘ └──────────────────────────┘ +``` + +Разделение решает два противоречия: on-chain-состояние обеспечивает +provable авторство + экономические стимулы (оплата за байт); off-chain +тела позволяют хранить мегабайты картинок без раздувания истории +блоков. Hosting relay получает плату автора (через CREATE_POST tx); +другие ноды могут реплицировать через gossipsub (роадмап). + +## Оплата постов + +Автор поста платит `BasePostFee + Size × PostByteFee` µT за публикацию. +Полный fee кредитуется на баланс hosting-релея. + +| Константа | Значение | Примечание | +|-----------|----------|-----------| +| `BasePostFee` | 1 000 µT (0.001 T) | Совпадает с MinFee (block-validation floor) | +| `PostByteFee` | 1 µT/byte | Размер = контент + attachment + ~128 B метаданных | +| `MaxPostSize` | 256 KiB | Hard cap, больше — 413 на `/feed/publish` | + +Пример: +- 200-байтовый текстовый пост → ~1 328 µT (~0.0013 T) +- Текст + 30 KiB WebP-картинка → ~31 848 µT (~0.032 T) +- Видео 200 KiB → ~205 128 µT (~0.21 T) + +## Server-side metadata scrubbing + +**Обязательная** для всех аттачментов на `/feed/publish`: + +- **Изображения** (JPEG/PNG/GIF/WebP): decode → downscale до 1080 px + → re-encode как JPEG Q=75 через stdlib. EXIF/GPS/ICC/XMP/MakerNote + стираются by construction (stdlib JPEG encoder не пишет их). +- **Видео/аудио**: переадресуется во внешний **FFmpeg-сайдкар** + (контейнер `docker/media-sidecar/`). Если сайдкар не настроен через + `DCHAIN_MEDIA_SIDECAR_URL` и оператор не выставил + `DCHAIN_ALLOW_UNSCRUBBED_VIDEO`, видео-аплоад отклоняется с 503. +- **MIME mismatch**: если magic-байты не соответствуют заявленному + `Content-Type` — 400. Защита от PDF'а, замаскированного под картинку. + +## Endpoints + +### `POST /feed/publish` + +Загружает тело поста, запускает скраббинг, сохраняет в feed-mailbox, +возвращает метаданные для последующей on-chain CREATE_POST tx. + +**Request:** +```json +{ + "post_id": "", + "author": "", + "content": "Text of the post", + "content_type": "text/plain", + "attachment_b64": "", + "attachment_mime": "image/jpeg", + "reply_to": "", + "quote_of": "", + "sig": "", + "ts": 1710000000 +} +``` + +`sig = Ed25519.Sign(author_priv, "publish:::")` + +где `raw_content_hash = sha256(content + raw_attachment_bytes)` — +клиентский хэш **до** серверного скраба. + +`ts` в пределах ±5 минут от серверного времени (anti-replay). + +**Response:** +```json +{ + "post_id": "", + "hosting_relay": "", + "content_hash": "", + "size": 1234, + "hashtags": ["golang", "dchain"], + "estimated_fee_ut": 2234 +} +``` + +Клиент затем собирает CREATE_POST tx с этими полями и сабмитит на +`/api/tx`. См. `buildCreatePostTx` в клиентской библиотеке. + +### `GET /feed/post/{id}` + +Тело поста со статами. + +**Response:** +```json +{ + "post_id": "...", + "author": "...", + "content": "...", + "content_type": "text/plain", + "hashtags": ["golang"], + "created_at": 1710000000, + "reply_to": "", + "quote_of": "", + "attachment": "", + "attachment_mime": "image/jpeg" +} +``` + +**Errors:** 404 если не найден; 410 если on-chain soft-deleted. + +### `GET /feed/post/{id}/attachment` + +Сырые байты аттачмента с корректным `Content-Type`. Используется +нативным image-loader'ом клиента: + +```html + +``` + +`Cache-Control: public, max-age=3600, immutable` — content-addressed +ресурс, кэш безопасен. + +### `POST /feed/post/{id}/view` + +Инкремент off-chain счётчика просмотров. Fire-and-forget, не требует +подписи (per-view tx был бы нереалистичен). + +**Response:** `{"post_id": "...", "views": 42}` + +### `GET /feed/post/{id}/stats?me=` + +Агрегат для отображения. `me` опционален — если передан, в ответе +будет `liked_by_me`. + +**Response:** +```json +{ + "post_id": "...", + "views": 127, + "likes": 42, + "liked_by_me": true +} +``` + +### `GET /feed/author/{pub}?limit=N&before=` + +Посты конкретного автора, newest-first. + +Пагинация: передавайте `before=` самого старого уже +загруженного поста, чтобы получить следующую страницу. + +**Response:** +```json +{"author": "", "count": 20, "posts": [FeedPostItem]} +``` + +### `GET /feed/timeline?follower=&limit=N&before=` + +Лента подписок: merged посты всех, на кого подписан follower, newest- +first. Требует, чтобы on-chain FOLLOW-транзакции уже скоммитились. + +Пагинация через `before` идентично `/feed/author`. + +### `GET /feed/trending?window=24&limit=N` + +Топ постов за последние N часов, ранжированы по `likes × 3 + views`. + +`window` — часы (1..168), по умолчанию 24. + +Пагинация **не поддерживается** — это топ-срез, не список. + +### `GET /feed/foryou?pub=&limit=N` + +Рекомендации. Простая эвристика v2.0.0: + +1. Кандидаты: посты последних 48 часов +2. Исключить: авторы, на которых уже подписан; уже лайкнутые; свои +3. Ранжировать: `likes × 3 + views + 1` (seed чтобы свежее вылезало) + +Никаких ML — база для `v2.2.0 Feed algorithm` (half-life decay, +mutual-follow boost, hashtag-affinity). + +### `GET /feed/hashtag/{tag}?limit=N` + +Посты, помеченные хэштегом `#tag`. Tag case-insensitive. +Хэштеги авто-индексируются на `/feed/publish` из текста (regex +`#[A-Za-z0-9_\p{L}]{1,40}`, dedup, cap 8 per post). + +## On-chain события + +Клиент сабмитит эти транзакции через обычный `/api/tx` endpoint. + +### `CREATE_POST` +```json +{ + "type": "CREATE_POST", + "fee": , + "payload": { + "post_id": "...", + "content_hash": "", + "size": 1234, + "hosting_relay": "", + "reply_to": "", + "quote_of": "" + } +} +``` + +Валидации on-chain: `size > 0 && size <= MaxPostSize`, +`fee >= BasePostFee + size × PostByteFee`, `reply_to` и `quote_of` +взаимно исключают друг друга. Дубликат `post_id` отклоняется. + +### `DELETE_POST` +```json +{"type": "DELETE_POST", "fee": 1000, "payload": {"post_id": "..."}} +``` +Только автор может удалить. Soft-delete (post остаётся в chain, но +помечен `deleted=true`; читатели получают 410). + +### `FOLLOW` / `UNFOLLOW` +```json +{"type": "FOLLOW", "to": "", "fee": 1000, "payload": {}} +``` +Двусторонний on-chain индекс: `follow::` + `followin::`. +`UNFOLLOW` зеркально удаляет оба ключа. + +### `LIKE_POST` / `UNLIKE_POST` +```json +{"type": "LIKE_POST", "fee": 1000, "payload": {"post_id": "..."}} +``` +Дубль-лайк отклоняется. Кэшированный counter `likecount:` +хранит O(1) счётчик. + +## Pricing / economics (резюме) + +| Операция | Cost | Куда уходит | +|----------|------|-------------| +| `CREATE_POST` | 1000 + size×1 µT | Hosting relay | +| `DELETE_POST` | 1000 µT | Block validator | +| `FOLLOW` / `UNFOLLOW` | 1000 µT | Block validator | +| `LIKE_POST` / `UNLIKE_POST` | 1000 µT | Block validator | +| `/feed/post/{id}/view` | 0 | Off-chain counter | + +Причина такого разделения: основные бизнес-действия (публикация) едут +на «хостера контента», а мелкие социальные tx (лайк/подписка) — на +валидатора сети, как обычные tx. + +## Клиентские вспомогательные функции + +В `client-app/lib/feed.ts`: +- `publishPost(params)` — подпись + POST /feed/publish +- `publishAndCommit(params)` — publish + CREATE_POST tx в одну операцию +- `fetchTimeline`, `fetchForYou`, `fetchTrending`, `fetchAuthorPosts`, + `fetchHashtag`, `fetchPost`, `fetchStats`, `bumpView` +- `buildCreatePostTx`, `buildDeletePostTx`, `buildFollowTx`, + `buildUnfollowTx`, `buildLikePostTx`, `buildUnlikePostTx` +- `likePost`, `unlikePost`, `followUser`, `unfollowUser`, `deletePost` + (build + submitTx bundled) diff --git a/docs/api/relay.md b/docs/api/relay.md index 2293c48..b353075 100644 --- a/docs/api/relay.md +++ b/docs/api/relay.md @@ -1,28 +1,83 @@ # Relay API -REST API для работы с шифрованными сообщениями через relay-сеть. +REST API для работы с зашифрованными 1:1-сообщениями через relay-сеть. -Сообщения шифруются E2E с использованием NaCl (X25519 + XSalsa20-Poly1305). Relay хранит зашифрованные конверты и доставляет их получателям. +Сообщения шифруются E2E с использованием NaCl (X25519 + XSalsa20-Poly1305). +Relay хранит зашифрованные конверты до 7 дней (настраиваемо через +`DCHAIN_MAILBOX_TTL_DAYS`) и доставляет их получателям по запросу или +через WebSocket push. -## Отправить сообщение +> **Это API для 1:1 чатов.** Для публичных постов и ленты — см. +> [`feed.md`](feed.md). -### `POST /relay/send` +Server-side guarantees (v1.0.x hardening): +- `envelope.ID` пересчитывается канонически (`sha256(nonce||ct)[:16]`) + на `mailbox.Store` — защита от content-replay. +- `envelope.SentAt` переписывается серверным `time.Now().Unix()` — клиент + не может back-date/future-date сообщения. +- `/relay/broadcast` и `/relay/inbox/*` обёрнуты в rate-limiter + (`withSubmitTxGuards` / `withReadLimit`); одиночный атакующий не может + через FIFO-eviction выбросить реальные сообщения получателя. +- `DELETE /relay/inbox/{id}` требует Ed25519-подпись владельца, связанную + с x25519-ключом через identity-реестр. -Зашифровать и отправить сообщение получателю. +## Опубликовать envelope + +### `POST /relay/broadcast` — **рекомендованный E2E-путь** + +Клиент запечатывает сообщение через NaCl `box` своим X25519-privkey на +публичный ключ получателя → отправляет готовый envelope. Сервер +проверяет размер, канонизирует id/timestamp, сохраняет в mailbox и +гошепит пирам. + +**Request body:** +```json +{ + "envelope": { + "id": "", + "sender_pub": "", + "recipient_pub": "", + "sender_ed25519_pub": "", + "fee_ut": 0, + "fee_sig": null, + "nonce": "", + "ciphertext": "", + "sent_at": 1710000000 + } +} +``` + +**Response:** `{"id": "", "status": "broadcast"}` + +> Поле `envelope.id` сервер **перезапишет** на +> `hex(sha256(nonce||ciphertext)[:16])`; клиент может прислать любое +> непустое значение. + +```bash +curl -X POST http://localhost:8081/relay/broadcast \ + -H "Content-Type: application/json" \ + -d @envelope.json +``` + +--- + +## Отправить plaintext через релей (legacy / non-E2E) + +### `POST /relay/send` — **⚠ НЕ E2E** + +Нода запечатывает сообщение **своим** X25519-ключом. Получатель не +сможет расшифровать без знания privkey релея, и отправитель не +аутентифицируется. Оставлено для backward-compat; в продакшн-чатах +используйте `/relay/broadcast`. **Request body:** ```json { "recipient_pub": "", - "msg_b64": "" + "msg_b64": "" } ``` -| Поле | Тип | Описание | -|------|-----|---------| -| `recipient_pub` | string | X25519 public key получателя (hex) | -| `msg_b64` | string | Сообщение в base64 | - ```bash MSG=$(echo -n "Hello, Bob!" | base64) curl -X POST http://localhost:8081/relay/send \ @@ -30,22 +85,15 @@ curl -X POST http://localhost:8081/relay/send \ -d "{\"recipient_pub\":\"$BOB_X25519\",\"msg_b64\":\"$MSG\"}" ``` -**Response:** -```json -{ - "id": "env-abc123", - "recipient_pub": "...", - "status": "sent" -} -``` +**Response:** `{"id": "env-abc123", "recipient_pub": "...", "status": "sent"}` --- -## Broadcast конверта +## Broadcast конверта (legacy alias) ### `POST /relay/broadcast` -Опубликовать pre-sealed конверт (для light-клиентов, которые шифруют на своей стороне). +См. выше. Это основной E2E-путь. **Request body:** ```json @@ -132,18 +180,35 @@ curl "http://localhost:8081/relay/inbox/count?pub=$MY_X25519" --- -### `DELETE /relay/inbox/{envID}?pub=` +### `DELETE /relay/inbox/{envID}?pub=` -Удалить сообщение из inbox. +Удалить сообщение из inbox. **Требует подписи** Ed25519-ключа, чья +identity связана с `?pub=` через on-chain identity-реестр — +защита от grief-DELETE любым знающим ваш pubkey. + +**Request body:** +```json +{ + "ed25519_pub": "", + "sig": "", + "ts": 1710000000 +} +``` + +Где `sig = Ed25519.Sign(priv, "inbox-delete:::")`. +`ts` должен быть в пределах ±5 минут от серверного времени (anti-replay). ```bash -curl -X DELETE "http://localhost:8081/relay/inbox/env-abc123?pub=$MY_X25519" +# подпись: printf 'inbox-delete:env-abc123:%s:%d' "$MY_X25519" "$TS" | sign +curl -X DELETE "http://localhost:8081/relay/inbox/env-abc123?pub=$MY_X25519" \ + -H "Content-Type: application/json" \ + -d "{\"ed25519_pub\":\"$MY_ED25519\",\"sig\":\"$SIG\",\"ts\":$TS}" ``` -**Response:** -```json -{"id": "env-abc123", "status": "deleted"} -``` +**Response:** `{"id": "env-abc123", "status": "deleted"}` + +**Errors:** `403` если подпись/идентичность не совпадает; `503` если +на ноде не настроен identity-resolver (DCHAIN_DISABLE_INBOX_DELETE). --- diff --git a/docs/architecture.md b/docs/architecture.md index 991d950..4672b78 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,16 +2,18 @@ ## Обзор -DChain — это L1-блокчейн для децентрализованного мессенджера. Архитектура разделена на четыре слоя: +DChain — это L1-блокчейн для децентрализованного мессенджера + социальной ленты. Архитектура разделена на четыре слоя: ``` ┌─────────────────────────────────────────────────────────────┐ -│ L3 Application — messaging, usernames, auctions, escrow │ +│ L3 Application — messaging, social feed, usernames, │ +│ auctions, escrow │ │ (Smart contracts: username_registry, governance, auction, │ -│ escrow; deployed on-chain via DEPLOY_CONTRACT) │ +│ escrow; native feed events; deployed on-chain) │ ├─────────────────────────────────────────────────────────────┤ -│ L2 Transport — relay mailbox, E2E NaCl encryption │ -│ (relay/, mailbox, GossipSub envelopes, RELAY_PROOF tx) │ +│ L2 Transport — relay mailbox (1:1 E2E) + │ +│ feed mailbox (public posts + attachments)│ +│ (relay/, media scrubber, GossipSub envelopes, RELAY_PROOF) │ ├─────────────────────────────────────────────────────────────┤ │ L1 Chain — PBFT consensus, WASM VM, BadgerDB │ │ (blockchain/, consensus/, vm/, identity/) │ @@ -189,6 +191,50 @@ Alice node1 (relay) Bob --- +## Социальная лента (v2.0.0) + +Публичные посты в Twitter/VK-стиле. Заменила channel-модель (каналы +удалены полностью). Живёт гибридно — метаданные on-chain, тела в relay +feed-mailbox: + +``` +Alice node1 (hosting relay) Bob + │ │ │ + │─ POST /feed/publish ──────▶│ │ + │ (body + EXIF-scrub) │ │ + │ │ store in feed-mailbox │ + │ │ (TTL 30 days) │ + │ │ │ + │◀── response: content_hash, │ │ + │ estimated_fee_ut, id │ │ + │ │ │ + │─ CREATE_POST tx ──────────▶│── PBFT commit ──────────▶ │ + │ (fee = 1000 + size × 1) │ │ + │ │ on-chain: post: │ + │ │ │ + │ │ │─ GET /feed/timeline + │ │◀── merge followed authors │ (from chain) + │ │ │ + │ │── GET /feed/post/{id} ───▶│ + │ │ (body from mailbox) │ +``` + +**Экономика:** +- Автор платит `BasePostFee(1000) + size × PostByteFee(1)` µT +- Вся плата уходит `hosting_relay` пубкею — компенсация за хранение +- `MaxPostSize = 256 KiB` — hard cap, защищает мейлбокс от abuse + +**Метаданные:** +- EXIF/GPS/camera-info удаляется обязательно на сервере (in-proc для + изображений, через FFmpeg-сайдкар для видео) +- Лайки / follows — on-chain транзакции (provable + anti-Sybil fee) +- Просмотры — off-chain counter в relay (on-chain было бы абсурдно) +- Хэштеги — авто-индекс при publish, inverted-index в BadgerDB + +Детали — [api/feed.md](api/feed.md). + +--- + ## Динамические валидаторы Сет валидаторов хранится on-chain в `validator:`. Любой текущий валидатор может добавить или убрать другого (ADD_VALIDATOR / REMOVE_VALIDATOR tx). После commit такого блока PBFT-движок перезагружает сет без рестарта ноды. diff --git a/docs/node/README.md b/docs/node/README.md index c676262..b2649f3 100644 --- a/docs/node/README.md +++ b/docs/node/README.md @@ -147,8 +147,12 @@ txchron:: → tx_id (recent-tx index) balance: → uint64 (µT) stake: → uint64 (µT) id: → JSON RegisterKeyPayload -chan: → JSON CreateChannelPayload -chan-member:: → "" +post: → JSON PostRecord (v2.0.0 social feed) +postbyauthor::: → "" (chrono index, newest-first scan) +follow:: → "" (A follows B) +followin:: → "" (reverse index for Followers()) +like:: → "" (presence) +likecount: → uint64 (cached counter, O(1) reads) contract: → JSON ContractRecord cstate:: → bytes clog::: → JSON ContractLogEntry diff --git a/docs/update-system.md b/docs/update-system.md index bce673f..cfe61b6 100644 --- a/docs/update-system.md +++ b/docs/update-system.md @@ -44,10 +44,10 @@ curl -s http://localhost:8080/api/well-known-version | jq . }, "protocol_version": 1, "features": [ - "access_token", "chain_id", "channels_v1", "contract_logs", - "fan_out", "identity_registry", "native_username_registry", - "onboarding_api", "payment_channels", "relay_mailbox", - "ws_submit_tx" + "access_token", "chain_id", "contract_logs", + "fan_out", "feed_v2", "identity_registry", "media_scrub", + "native_username_registry", "onboarding_api", "payment_channels", + "relay_broadcast", "relay_mailbox", "ws_submit_tx" ], "chain_id": "dchain-ddb9a7e37fc8" } diff --git a/node/api_well_known_version.go b/node/api_well_known_version.go index dd7edec..d89b514 100644 --- a/node/api_well_known_version.go +++ b/node/api_well_known_version.go @@ -12,7 +12,7 @@ // returned forever (even if the implementation moves around internally). The // client uses them as "is this feature here or not?", not "what version is // this feature at?". Versioning a feature is done by shipping a new tag -// (e.g. "channels_v2" alongside "channels_v1" for a deprecation window). +// (e.g. "feed_v3" alongside "feed_v2" for a deprecation window). // // Response shape: // @@ -55,15 +55,17 @@ const ProtocolVersion = 1 // a breaking successor (e.g. `channels_v1`, not `channels`). var nodeFeatures = []string{ "access_token", // DCHAIN_API_TOKEN gating on writes (+ optional reads) - "channels_v1", // /api/channels/:id + /members with X25519 enrichment "chain_id", // /api/network-info returns chain_id "contract_logs", // /api/contract/:id/logs endpoint "fan_out", // client-side per-recipient envelope sealing + "feed_v2", // social feed: posts, likes, follows, timeline, trending, for-you "identity_registry", // /api/identity/:pub returns X25519 pub + relay hints + "media_scrub", // mandatory EXIF/GPS strip on /feed/publish "native_username_registry", // native:username_registry contract "onboarding_api", // /api/network-info for joiner bootstrap "payment_channels", // off-chain payment channel open/close - "relay_mailbox", // /relay/send + /relay/inbox + "relay_broadcast", // /relay/broadcast (E2E envelope publish) + "relay_mailbox", // /relay/inbox (read), /relay/send (legacy non-E2E) "ws_submit_tx", // WebSocket submit_tx op }