Files
psf-memo-indexer/dev-docs/architecture.md
T

178 lines
6.3 KiB
Markdown
Raw Normal View History

2026-06-02 16:54:13 -07:00
# Architecture
## System context
```mermaid
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](https://github.com/Permissionless-Software-Foundation/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](https://christroutner.github.io/trouts-blog/blog/clean-architecture): dependencies point inward; framework and I/O live at the edges.
### psf-memo-indexer layers
```text
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
2026-06-02 17:11:42 -07:00
index-blocks.js — processBlock, processMemoTx (parallel per block)
filter-block.js — filterMemoTxs (parallel pre-check, block order preserved)
2026-06-02 16:54:13 -07:00
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
```text
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](./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 |
2026-06-02 17:11:42 -07:00
| `p-queue` / `p-retry` | Indexer | Parallel block filter and parallel per-tx processing within a block |
2026-06-02 16:54:13 -07:00
| `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
```text
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`.