mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-indexer.git
synced 2026-09-21 16:52:02 -07:00
178 lines
6.3 KiB
Markdown
178 lines
6.3 KiB
Markdown
# 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
|
||
index-blocks.js — processBlock, processMemoTx (parallel per block)
|
||
filter-block.js — filterMemoTxs (parallel pre-check, block order preserved)
|
||
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 |
|
||
| `p-queue` / `p-retry` | Indexer | Parallel block filter and parallel per-tx processing within a block |
|
||
| `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`.
|