DChain single-node blockchain + React Native messenger client. Core: - PBFT consensus with multi-sig validator admission + equivocation slashing - BadgerDB + schema migration scaffold (CurrentSchemaVersion=0) - libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1) - Native Go contracts (username_registry) alongside WASM (wazero) - WebSocket gateway with topic-based fanout + Ed25519-nonce auth - Relay mailbox with NaCl envelope encryption (X25519 + Ed25519) - Prometheus /metrics, per-IP rate limit, body-size cap Deployment: - Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus - 3-node dev compose (docker-compose.yml) with mocked internet topology - 3-validator prod compose (deploy/prod/) for federation - Auto-update from Gitea via /api/update-check + systemd timer - Build-time version injection (ldflags → node --version) - UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER) Client (client-app/): - Expo / React Native / NativeWind - E2E NaCl encryption, typing indicator, contact requests - Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch Documentation: - README.md, CHANGELOG.md, CONTEXT.md - deploy/single/README.md with 6 operator scenarios - deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design - docs/contracts/*.md per contract
9.2 KiB
9.2 KiB
DChain CHANGELOG
Consolidated record of what landed. Replaces the now-deleted
REFACTOR_PLAN.md, NODE_ONBOARDING.md, and ROADMAP.md — every numbered
item there is either shipped (listed below) or explicitly deferred.
Production-ready stack (shipped)
Consensus & chain
- PBFT multi-sig validator admission.
ADD_VALIDATORrequires ⌈2/3⌉ cosigs from the current set + candidate must have ≥MinValidatorStake(1 T) locked via STAKE. Same gate on forcedREMOVE_VALIDATOR; self- removal stays unilateral. - Equivocation slashing.
SLASHtx withreason=equivocationcarries both conflicting PREPARE/COMMIT messages as evidence;ValidateEquivocationverifies on-chain — any node can report, no trust. Offender's stake is burned and they're evicted from the set. - Liveness tracking. PBFT records per-validator last-seen seqNum;
LivenessReport()+MissedBlocks()surface stalemates. Exposed viadchain_max_missed_blocksPrometheus gauge. - Fair mempool. Per-sender FIFO queues drained round-robin into proposals; one spammer can't starve others.
- Block-reward fix. Synthetic BLOCK_REWARD transactions use
From=""so self-validators don't appear to pay themselves in history.
Storage & stability
- Re-entrant-deadlock fix. Dedicated
configMuandnativeMuseparate fromc.mu— applyTx can safely read config/native registry whileAddBlockholds the write lock. - BadgerDB tuning.
WithValueLogFileSize(64 MiB)+WithNumVersionsToKeep(1)- background
StartValueLogGCevery 5 minutes + one-shotCompactNow()at startup. Reclaims gigabytes from upgraded nodes automatically.
- background
TipIndex()— lock-free reads so/api/blocksand/api/txs/recentnever hang even whenAddBlockis stuck.- Chronological tx index (
txchron:<block20d>:<seq04d>).RecentTxsruns in O(limit) instead of O(empty blocks) — important when tps is low. - WASM VM timeout +
WithCloseOnContextDone. Any contract call aborts at 30 s hard cap; gas metering evasion can no longer freeze the chain.
Native contracts
native:username_registry(v2.1.0). Replaces WASM registry — 100× faster, no VM failure surface.register(name)requires exacttx.Amount = 10 000 µT(burned, visible in history). Min length 4, lowercasea-z 0-9 _ -, first char letter, reserved words blacklist.- Native dispatcher in
applyTx→ checksnative:*IDs first, falls through to WASM VM otherwise. ABI JSON + contract metadata surfaced via/api/contracts/:idwith"native": trueflag. - Well-known auto-discovery.
/api/well-known-contractsreturns canonical contract IDs indexed by ABI name; native always wins. Client auto-syncs on connect.
WebSocket gateway (push-based UX)
GET /api/ws— persistent bidirectional JSON-framed connection.- Topics:
blocks,tx,addr:<pub>,inbox:<x25519>,contract_log,contract:<id>,typing:<x25519>. authop. Client signs server-issued nonce with Ed25519; hub binds connection to pubkey so scoped subscriptions (addr:*,inbox:*,typing:*) are accepted only for owned identities.submit_txop. Low-latency tx submission with correlatedsubmit_ackframe; removes the HTTP round-trip. Client falls back to POST/api/txautomatically if WS is down.- Typing indicators. Ephemeral
typingop, authenticated, scoped to recipient. Mobile client shows "печатает…" in chat header. - Per-connection quotas. Max 10 connections / IP, 32 subs / connection.
Bounded outbox drops oldest on overflow with
{event:"lag"}notice. - Fanout mirrors SSE.
eventBusdispatches to SSE + WS + future consumers from one emit site.
Relay mailbox
- Push notifications.
Mailbox.SetOnStorehook →wsHub.EmitInbox(...)on every fresh envelope. Client'suseMessagessubscribes instead of polling every 3 s. - Relay TTL.
REGISTER_RELAYand HEARTBEAT (from registered relays) refresh arelayhb:<pub>timestamp;/api/relaysfilters anything older than 2 hours. Stale relays are delisted automatically.
Node onboarding
--join <url1,url2,…>— multi-seed bootstrap. Tries each URL in order, persists the live list to<db>/seeds.jsonon first success so subsequent restarts don't need the CLI flag./api/network-info— one-shot payload (chain_id, genesis_hash, validators, peers, contracts, stats) for joiners./api/peers— live libp2p peer list with multiaddrs.- Genesis-hash verification. A node with expected hash aborts if its
local block 0 doesn't match (protection against forged seeds). Override
with
--allow-genesis-mismatchfor migrations. - Gap-fill on gossip. Blocks with
b.Index > tip+1triggerSyncFromPeerFullto the gossiping peer (rate-limited 1 per peer per minute). Nodes recover from brief outages without restart.
API surface & security
- Rate limiter (
node/api_guards.go). Per-IP token bucket on/api/txand/v2/chain/transactions: 10 tx/s, burst 20. - Request-size cap.
/api/txbody ≤ 64 KiB. - Timestamp validation. ±1 h window on submit, refuses clock-skewed or replayed txs.
- Humanised errors in client.
humanizeTxErrortranslates 429 / 400+timestamp / 400+signature / network-failure into Russian user- facing text.
Observability & ops
- Prometheus
/metrics. Zero-dep in-tree implementation (node/metrics.go) with counters (blocks, txs, submit accepted/rejected), gauges (ws connections, peer count, max missed blocks), histogram (block commit seconds). - Load test.
cmd/loadtest— N concurrent WS clients with auth + scoped subs + TRANSFER at rate. Validates chain advances, reject rate, ws-drop count. Smoke at 20 clients × 15 s → 136 accepted / 0 rejected. - Structured logging.
--log-format=text|jsonflag. JSON mode routes bothslog.*and legacylog.Printfthrough one JSON handler for Loki/ELK ingestion. - Observer mode.
--observer(envDCHAIN_OBSERVER) disables PBFT producer + heartbeat + auto-relay-register; node still gossips and serves HTTP/WS. For horizontally-scaling read-only API frontends.
Deployment
deploy/single/— one-node production bundle:- Same
Dockerfile.slimas the cluster variant. - Compose stack: 1 node + Caddy + optional Prometheus/Grafana.
- Supports three operator-chosen access modes:
- Public (no token) — anyone can read + submit.
- Public reads, token-gated writes (
DCHAIN_API_TOKENset) — reads stay open, submit tx requiresAuthorization: Bearer. - Fully private (
DCHAIN_API_TOKEN+DCHAIN_API_PRIVATE) — every endpoint requires the token.
- Runbook covers three scenarios: genesis node, joiner, private.
- Same
deploy/prod/— 3-validator cluster for federations/consortiums.- Access-token middleware in
node/api_guards.go:withWriteTokenGuardgates POST /api/tx and WS submit_tx.withReadTokenGuardgates reads when--api-privateis set.- WS upgrade applies the same check;
submit_txops on a non-authenticated connection are rejected withsubmit_ackrejected.
- All CLI flags accept
DCHAIN_*env fallbacks for Docker-driven configuration, including the newDCHAIN_API_TOKEN/DCHAIN_API_PRIVATE.
Client (React Native / Expo)
- WebSocket module
lib/ws.tswith reconnect, auto-resubscribe, auto-auth on reconnect. useBalance,useContacts,useMessages— all push-based with HTTP polling fallback after 15 s disconnect.useWellKnownContracts— auto-syncssettings.contractIdwith node's canonical registry.- Safe-area-aware layout throughout. Tab bar no longer hides under home indicator on iPhone.
- Username purchase UI with live validation (min 4, first letter, charset).
- Transaction detail sheet with system-tx handling (BLOCK_REWARD shows "Сеть" as counterpart, not validator's self-pay).
Deliberately deferred
- Split
blockchain/chain.gointostate/,applytx/,mempool/,index/,events/subpackages. A ~2.5k-line single-file refactor is high risk; to be attempted after the chain has been running in prod long enough that regressions there would be caught fast. - Full
p2p/rewrite with typed event channel. The libp2p integration works; event-bus was added at the node layer instead (seenode/events.go). - Full mempool admission pricing (gas-priced priority queues). Current fair round-robin works within spam-proofing needs.
Compatibility notes
- BadgerDB tuning is compatible with databases created by previous
versions; the first run reclaims old value-log space via
CompactNow(). AddValidatorPayload/RemoveValidatorPayloadgained acosigsfield; older payloads without it still parse (default empty), but will fail the ⌈2/3⌉ threshold on chains with >1 validator.BLOCK_REWARDtransactions changed fromFrom=validatortoFrom="". Old indexed records keep their previousFrom; new ones use the new shape. Explorer/client handle both.- Registration fee for usernames moved from internal
ctx.Debittotx.Amount. The WASM username_registry is superseded bynative:username_registry; well-known endpoint returns the native version as canonical.