Files
psf-memo-indexer/dev-docs/architecture.md
T
2026-06-02 16:54:13 -07:00

6.2 KiB
Raw Blame History

Architecture

System context

flowchart TB
  subgraph node [BCH_full_node]
    RPC[JSON-RPC]
    ZMQ[ZMQ_rawtx_rawblock]
  end

  subgraph indexer [psf_memo_indexer]
    BI[Block_indexer_process]
    TI[TX_indexer_process]
    BI -->|GET_tx-start| TI
  end

  subgraph db [psf_memo_db]
    Koa[Koa_REST]
    LDB[(LevelDB_instances)]
    Koa --> LDB
  end

  RPC --> BI
  RPC --> TI
  ZMQ --> BI
  ZMQ --> TI
  BI -->|axios_level_CRUD| Koa
  TI -->|axios_level_CRUD| Koa

Two-process indexer design

The indexer splits work the same way as psf-slp-indexer-g2:

Process Responsibility Why separate
Block indexer Historical catch-up (IBD) and confirmed blocks via ZMQ Heavy, sequential block work; must finish before mempool indexing is trustworthy for “at tip”
TX indexer Unconfirmed transactions via ZMQ High volume, duplicate ZMQ events; isolated so IBD memory/CPU does not contend with mempool flood

Coordination is deliberately minimal: after IBD, the block indexer issues GET http://{TX_REST_API_IP}:{TX_REST_API_PORT}/tx-start. The TX indexer polls that flag every two seconds until set, then subscribes to ZMQ.

Tradeoff: No shared memory or message queue between processes—only HTTP and the database. Simplicity and operational parity with SLP outweigh lower latency startup.

Clean Architecture (indexer)

Both repos follow Clean Architecture: dependencies point inward; framework and I/O live at the edges.

psf-memo-indexer layers

Entry points (framework)
  psf-memo-block-indexer.js
  psf-memo-tx-indexer.js
        │
        ▼
Controllers
  keyboard.js          — graceful stop (q key)
  tx-rest-api.js       — Express /tx-start
        │
        ▼
Use cases
  index-blocks.js      — processBlock, processMemoTx
  filter-block.js      — filterMemoTxs (parallel pre-check)
  state.js             — synced block height
  action-types/*.js    — per Memo action handlers
  utils.js
        │
        ▼
Adapters
  rpc.js               — full node JSON-RPC
  zmq.js               — @psf/bitcoincash-zmq-decoder
  transaction.js       — fetch tx + memo detection cache
  status-db.js, *-db   — axios → psf-memo-db
  tx-indexer.js        — start signal to TX process
  backup-db.js         — POST /level/backup
        │
        ▼
Libraries
  memo-parser.js       — OP_RETURN pushdata, signer address
  memo-codes.js        — action prefixes and limits

Rule: Use cases contain business rules (valid pushdata counts, reply parent links, like tips). Adapters only move bytes on the network or disk API.

psf-memo-db layers

index.js → bin/server.js (Koa)
        │
        ▼
Controllers
  rest-api/index.js
  rest-api/level/*     — CRUD handlers
  rest-api/health/
        │
        ▼
Use cases
  index.js             — minimal stub (parity with psf-slp-db)
        │
        ▼
Adapters
  level-db.js          — open/close LevelDB instances
  db-backup.js         — zip / restore

Level CRUD bypasses use cases intentionally—same as psf-slp-dbs /level controller calling adapters.level.*Db.put directly. The DB service is a thin persistence plane, not a domain model server.

Repository layout (indexer)

Path Purpose
config/index.js Environment-driven settings
src/lib/ Pure protocol parsing (no I/O)
src/adapters/ External systems
src/use-cases/ Indexing orchestration and handlers
src/controllers/ HTTP and keyboard
production/docker/ Docker Compose, per-service Dockerfile and .env
test/unit/ Mocha + c8

Repository layout (database)

Path Purpose
bin/server.js Koa bootstrap (mirrors psf-slp-db)
config/env/ SVC_ENV profiles
src/adapters/level-db.js Twelve LevelDB stores under leveldb/current/
src/controllers/rest-api/level/ Generic CRUD + status + backup routes
leveldb/current/{name}/ Runtime data (gitignored)
leveldb/zips/ Epoch backups

LevelDB schema (summary)

Each store is a separate LevelDB with JSON values. Keys are txids, addresses, or composite strings depending on entity. Full detail: psf-memo-db.md.

Store Typical key Value role
status status syncedBlockHeight, chainBlockHeight, startBlockHeight
posts txid Author address, text, timestamp
postParents child txid Parent txid (reply)
postChildren parent txid Reverse index for replies
likes like txid Post txid, liker, optional tip
names address Display name + provenance txid
profiles address Profile text
profilePics address Avatar URL
follows follower:followeePkHash Follow/unfollow event
rooms composite Topic posts and topic follows
processErrors txid Validation / parse failures
ptxs txid Idempotency marker (already processed)

External dependencies

Package Used by Role
@psf/bch-js Indexer Script decompile for signer address; optional REST
@psf/bitcoincash-zmq-decoder Indexer Decode rawtx / rawblock ZMQ messages
zeromq Indexer Subscribe to full node
@chris.troutner/retry-queue Indexer Retry RPC on transient failure
axios Indexer psf-memo-db REST client
p-queue / p-retry Indexer Parallel block filter with retries
express Indexer TX indexer control API
level psf-memo-db Embedded JSON LevelDB
koa + koa-router psf-memo-db REST server

Not used: slp-parser, Lokad ID checks, DAG sorting, token UTXO graphs.

Deployment topology

production/docker/
├── docker-compose.yml
├── memo-db/          → build context: ../../psf-memo-db
├── block-indexer/    → build context: indexer repo root
└── tx-indexer/

Volumes mount .env and start scripts per service, matching the SLP production pattern. LevelDB data can persist under production/data/leveldb.