6.2 KiB
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-db’s /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.