vsecoder 9e86c93fda test(feed): end-to-end integration + two-node propagation (Phase B hardening)
Adds two integration-test files that exercise the full feed stack over
real HTTP requests, plus a fix to the publish signature model that the
EXIF scrubbing test surfaced.

Bug fix — api_feed.go publish signature flow
  Previously: server scrubbed the attachment → computed content_hash
  over the SCRUBBED bytes → verified the author's signature against
  that hash. But the client, not owning the scrubber, signs over the
  RAW upload. The two hashes differ whenever scrub touches the bytes
  (which it always does for images), so every signed upload with an
  image was rejected as "signature invalid".

  Fixed order:
    1. decode attachment from base64
    2. compute raw_content_hash over Content + raw attachment
    3. verify author's signature against raw_content_hash
    4. scrub attachment (strips EXIF / re-encodes)
    5. compute final_content_hash over Content + scrubbed attachment
    6. return final hash in response for the on-chain CREATE_POST tx

  The signature proves the upload is authentic; the final hash binds
  the on-chain record to what readers actually download.

node/feed_e2e_test.go
  In-process harness: real BadgerDB chain + feed mailbox + media
  scrubber + httptest.Server with RegisterFeedRoutes. Tests drive
  it via real http.Post / http.Get so rate limiters, auth, scrubber,
  and handler code all run on the happy path.

  Tests:
  - TestE2EFullFlow — publish → CREATE_POST tx → body fetch → view
    bump → stats → author list → soft-delete → 410 Gone on re-fetch
  - TestE2ELikeUnlikeAffectsStats — on-chain LIKE_POST bumps /stats,
    liked_by_me reflects the caller
  - TestE2ETimeline — follow graph, merged timeline newest-first
  - TestE2ETrendingRanking — likes × 3 + views puts hot post at [0]
  - TestE2EForYouFilters — excludes own posts + followed authors +
    already-liked posts; surfaces strangers
  - TestE2EHashtagSearch — tag returns only tagged posts
  - TestE2EScrubberStripsEXIF — injects SUPERSECRETGPS canary into a
    JPEG APP1 segment, uploads via /feed/publish, reads back — asserts
    canary is GONE from stored attachment. This is the privacy-critical
    regression gate: if it ever breaks, GPS coordinates leak.
  - TestE2ERejectsMIMEMismatch — PNG labelled as JPEG → 400
  - TestE2ERejectsBadSignature — wrong signer → 403
  - TestE2ERejectsStaleTimestamp — 1-hour-old ts → 400 (anti-replay)

node/feed_twonode_test.go
  Simulates two independent nodes sharing block history (gossip via
  same-block AddBlock on both chains). Verifies the v2.0.0 design
  contract: chain state replicates, but post BODIES live only on the
  hosting relay.

  Tests:
  - TestTwoNodePostPropagation — Alice publishes on A; B's chain sees
    the record; B's HTTP /feed/post/{id} returns 404 (body is A's);
    fetch from A succeeds using hosting_relay field from B's chain
    lookup. Documents the client-side routing contract.
  - TestTwoNodeLikeCounterSharedAcrossNodes — Bob likes from Node B;
    both A's and B's /stats show likes=1. Proves engagement aggregates
    are chain-authoritative, not per-relay.
  - TestTwoNodeFollowGraphReplicates — FOLLOW tx propagates, /timeline
    on B returns A-hosted posts with metadata (no body, as designed).

Coverage summary
  Publish flow (sign → scrub → hash → store):          ✓
  CREATE_POST on-chain fee accounting:                 ✓
  Like / Unlike counter consistency:                   ✓
  Follow graph → timeline merge:                       ✓
  Trending ranking by likes × 3 + views:               ✓
  For You exclusion rules (self, followed, liked):     ✓
  Hashtag inverted index:                              ✓
  View counter increment + stats aggregate:            ✓
  Soft-delete → 410 Gone:                              ✓
  Metadata scrubbing (EXIF canary):                    ✓
  MIME mismatch rejection:                             ✓
  Signature authenticity:                              ✓
  Timestamp anti-replay (±5 min window):               ✓
  Two-node block propagation:                          ✓
  Cross-node body fetch via hosting_relay:             ✓
  Likes aggregation across nodes:                      ✓

All 7 test packages green: blockchain consensus identity media node
relay vm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:27:00 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00
2026-04-17 14:16:44 +03:00

DChain

Блокчейн-стек для децентрализованного мессенджера:

  • PBFT консенсус с multi-sig validator governance и equivocation slashing
  • Native Go контракты рядом с WASM (wazero) — нулевая задержка для системных сервисов типа username_registry
  • WebSocket push API — клиент не опрашивает, все события прилетают на соединение
  • E2E-шифрованный relay mailbox на libp2p gossipsub с TTL live-detection
  • Система обновлений: build-time версия → /api/well-known-version, peer-version gossip, /api/update-check против Gitea releases, update.sh с semver guard
  • Prometheus /metrics, Caddy auto-HTTPS, observer mode, load-test

Содержание


Быстрый старт

Одна нода в Docker, HTTP API на localhost:8080, Explorer UI и Swagger открыты:

# 1. Собираем prod-образ
docker build -t dchain-node-slim -f deploy/prod/Dockerfile.slim .

# 2. Ключ ноды (один раз)
mkdir -p keys
docker run --rm --entrypoint /usr/local/bin/client \
  -v "$PWD/keys:/out" dchain-node-slim \
  keygen --out /out/node.json

# 3. Запуск (genesis-валидатор)
docker run -d --name dchain --restart unless-stopped \
  -p 4001:4001 -p 8080:8080 \
  -v dchain_data:/data \
  -v "$PWD/keys:/keys:ro" \
  -e DCHAIN_GENESIS=true \
  -e DCHAIN_ANNOUNCE=/ip4/127.0.0.1/tcp/4001 \
  dchain-node-slim \
  --db=/data/chain --mailbox-db=/data/mailbox --key=/keys/node.json \
  --relay-key=/data/relay.json --listen=/ip4/0.0.0.0/tcp/4001 --stats-addr=:8080

# 4. Проверка
open http://localhost:8080/            # Explorer
open http://localhost:8080/swagger     # Swagger UI
curl -s http://localhost:8080/api/well-known-version | jq .

3-node dev-кластер (для тестов PBFT кворума, slashing, federation): docker compose up --build -d — см. docs/quickstart.md.

Продакшен деплой

Два варианта, по масштабу.

🔸 Single-node (deploy/single/)

Рекомендуется для личного/первого узла. Один узел + Caddy TLS + опциональный Prometheus. Полный runbook с 6 сценариями (публичная с UI, headless, полностью приватная, genesis, joiner, auto-update) — в deploy/single/README.md.

Модели доступа

Режим DCHAIN_API_TOKEN DCHAIN_API_PRIVATE Поведение
Public (default) не задан Все могут читать и писать
Token writes задан false Читать — любой; submit tx — только с токеном
Fully private задан true Всё требует Authorization: Bearer <token>

UI / Swagger — включать или нет?

Нужно DCHAIN_DISABLE_UI DCHAIN_DISABLE_SWAGGER Открыто
Публичная с эксплорером + docs не задано не задано / Explorer, /swagger, /api/*, /metrics
Headless API-нода с OpenAPI true не задано /swagger, /api/*, /metrics
Личная hardened true true /api/* + /metrics

Флаги читаются и из CLI (--disable-ui, --disable-swagger), и из env. /api/* JSON-поверхность регистрируется всегда — отключить её можно только на уровне Caddy / firewall.

Auto-update

# node.env — когда проект в Gitea
DCHAIN_UPDATE_SOURCE_URL=https://gitea.example.com/api/v1/repos/OWNER/REPO/releases/latest
UPDATE_ALLOW_MAJOR=false
# Hourly systemd timer с 15-мин jitter
sudo cp deploy/single/systemd/dchain-update.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now dchain-update.timer

Подробно: docs/update-system.md + deploy/UPDATE_STRATEGY.md.

🔹 Multi-validator (deploy/prod/)

3 validator'а в PBFT-кворуме, Caddy с ip_hash для WS-стикинесса и least-conn для REST. Для федераций / консорциумов — см. deploy/prod/README.md и docs/node/multi-server.md.

Архитектура

┌────────────┐    libp2p     ┌────────────┐    libp2p     ┌────────────┐
│   node-A   │◄─────pubsub──►│   node-B   │◄─────pubsub──►│   node-C   │
│ validator  │               │ validator  │               │ validator  │
│  + relay   │               │  + relay   │               │  + relay   │
└─────┬──────┘               └─────┬──────┘               └─────┬──────┘
      │                            │                            │
      │ HTTPS / wss (via Caddy)    │                            │
      ▼                            ▼                            ▼
    mobile / web / CLI clients   (ip_hash для WS, least-conn для REST)

Четыре слоя: network → chain → transport → app. Детали — в docs/architecture.md.

Основные пакеты:

Путь Роль
blockchain/ Блочная машина, applyTx, native-контракты, schema-migrations
consensus/ PBFT (Pre-prepare → Prepare → Commit), мемпул, equivocation
p2p/ libp2p host, gossipsub, sync протокол, peer-version gossip
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
identity/ Ed25519 + X25519 keypair, tx signing
economy/ Fee model, rewards
wallet/ Optional payout wallet (отдельный ключ)

REST / WebSocket API

Обзор (полный reference — docs/api/README.md):

Chain + stats

Endpoint Описание
GET /api/netstats height, total_txs, supply, validator_count
GET /api/network-info chain_id, genesis, peers, validators, contracts
GET /api/blocks?limit=N / /api/block/{index} Блоки
GET /api/tx/{id} / /api/txs/recent?limit=N Транзакции
GET /api/address/{pub_or_addr} Баланс + история
GET /api/validators Validator set
GET /api/peers Connected peers + их версии
POST /api/tx Submit signed tx (rate-limited, size-capped)

Discovery + update

Endpoint Описание
GET /api/well-known-version {node_version, build{}, protocol_version, features[], chain_id}
GET /api/well-known-contracts {name → contract_id} map
GET /api/update-check Diff с Gitea release (см. docs/update-system.md)

Real-time

  • GET /api/wsWebSocket (recommended). Ops: auth, subscribe, unsubscribe, submit_tx, typing, ping. Push: block, tx, inbox, typing, submit_ack.
  • GET /api/events — SSE legacy one-way.

Scoped WS-топики (addr:, inbox:, typing:) требуют auth через Ed25519-nonce; публичные (blocks, tx, contract_log) — без.

Docs / UI

  • GET /swaggerSwagger UI (рендерится через swagger-ui-dist).
  • GET /swagger/openapi.json — сырая OpenAPI 3.0 спека.
  • GET / — block-explorer HTML (выключается DCHAIN_DISABLE_UI=true).

CLI

client keygen               --out key.json
client --version            # → dchain-client vX.Y.Z (commit=... date=...)
client balance              --key key.json --node URL
client transfer             --key key.json --to <pub> --amount <µT> --node URL
client call-contract        --key key.json --contract native:username_registry \
                            --method register --arg alice --amount 10000 \
                            --node URL
client add-validator        --key key.json --target <pub> --cosigs pub:sig,pub:sig
client admit-sign           --key validator.json --target <candidate-pub>
client deploy-contract      --key key.json --wasm ./my.wasm --abi ./my_abi.json --node URL
# полный список — `client` без аргументов, или в docs/cli/README.md

Все флаги node — в docs/node/README.md, либо node --help.

Мониторинг

Prometheus endpoint /metrics на каждой ноде. Ключевые метрики:

dchain_blocks_total                  # committed blocks count
dchain_txs_total                     # tx count
dchain_tx_submit_accepted_total
dchain_tx_submit_rejected_total
dchain_ws_connections                # current WS sockets
dchain_peer_count_live               # live libp2p peer count
dchain_max_missed_blocks             # worst validator liveness gap
dchain_block_commit_seconds          # histogram of AddBlock time

Grafana + Prometheus поднимаются вместе с нодой через docker compose --profile monitor up -d (см. deploy/single/docker-compose.yml).

Тесты

# Unit + integration
go test ./...                       # blockchain + consensus + identity + relay + vm
go vet ./...                        # static checks

# End-to-end load (3-node dev cluster должен быть поднят):
docker compose up --build -d
go run ./cmd/loadtest \
  --node http://localhost:8081 \
  --funder testdata/node1.json \
  --clients 50 --duration 60s

Документация

Полный справочник в docs/ — см. docs/README.md как оглавление. Ключевые документы:

Документ О чём
docs/quickstart.md Три пути: локально, single-node prod, федерация
docs/architecture.md 4 слоя, consensus, storage, gas model
docs/update-system.md Versioning + update-check + semver guard
docs/api/README.md REST + WebSocket reference
docs/cli/README.md CLI команды и флаги
docs/node/README.md Запуск ноды (native + Docker)
docs/contracts/README.md Системные + WASM контракты
docs/development/README.md SDK для своих контрактов
deploy/single/README.md Single-node operator runbook
deploy/prod/README.md Multi-validator federation
deploy/UPDATE_STRATEGY.md Forward-compat design (4 слоя)
Description
No description provided
Readme 1.8 MiB
Languages
Go 57.7%
TypeScript 28.1%
JavaScript 5.8%
HTML 3.1%
CSS 2.7%
Other 2.6%