Self-hostable Massa blockchain indexer and explorer (Indexer V2).
This is the production stack that powers a full historical view of Massa mainnet: a Rust indexer that streams a node’s public gRPC API into a local RocksDB, plus a React explorer that consumes the indexer’s REST + SSE API.
| Component | Path | Purpose |
|---|---|---|
| Indexer | massa-indexer/ |
Streams filled blocks / execution / transfers from a Massa node, persists them in RocksDB, serves /v1/* (REST + SSE) and optional peer gRPC on :9443. |
| Explorer | massa-explorer/ |
React / TypeScript SPA (Vite) that talks to the indexer API. |
| Protos | massa-proto/ |
Vendored .proto files (node public API + indexer peer RPC). |
| Nginx samples | nginx/ |
Reference reverse-proxy / TLS / rate-limit configs. |
Docs in this repo
| Doc | What it is |
|---|---|
spec.md |
Authoritative functional contract (API, schema, completeness, peers, legacy import). |
explanation.md |
Design rationale — why each choice was made. |
ARCHITECTURE.md |
Physical 3-host deployment: hardware, systemd, storage, peering, day-2 ops. |
SECURITY.md |
Threat model and hardening checklist. |
OpenAPI for the live REST surface is served by every indexer at
/v1/openapi.json and rendered in the explorer under API.
Not in this repository (by design)
Massa node source/binaries, RocksDB data, logs, AWS credentials, and node private keys. Those stay on the deployment hosts. The Massa node is an external dependency — build/run it frommassalabs/massa(with theexecution-infofeature) next to the indexer.
Each production host is fully self-contained — no shared DB, no leader:
┌───────────────────────────────────────────────────┐
│ one physical host │
│ │
:80 ─────► nginx ────► (static dist) Massa Explorer SPA │
│ │ │
│ └──► /v1/* (REST + SSE) ───► massa-indexer │
│ │ │
│ │ gRPC │
│ ▼ │
│ massa-node │
│ (execution-info) │
└───────────────────────────────────────────────────┘
Data plane
- Live ingest — indexer subscribes to the node’s public gRPC streams
(
NewFilledBlocks,NewSlotExecutionOutputs,NewTransfersInfo, …). - RocksDB — fixed-length binary keys, protobuf values, secondary indexes
(by address / op / block / transfer). Lives under
/data/indexer/rocksdbon production hosts (never in git). - REST + SSE —
/v1/*for queries;/v1/slots/stream(and friends) for live UI. - Peer sync — optional indexer↔indexer gRPC on
:9443. Each box walks slots head→genesis and asks peers to fill gaps. Peers are free; they do not replace the local node. - Legacy one-shot (optional) — a single host may run a DynamoDB importer
once to backfill pre-indexer history. Credentials come from a host-local
systemd
EnvironmentFile, never from this repo. After completion, disable it (INDEXER_LEGACY_DDB_ENABLED=false).
Current deployment topology (details in ARCHITECTURE.md):
| Host | Where | Notes |
|---|---|---|
indexer1 |
LAN 192.168.0.19 |
Node + indexer + explorer; was the AWS legacy importer host (now disabled). |
indexer2 |
LAN 192.168.0.29 |
Node + indexer + explorer; peers with indexer1 on LAN. |
indexer3 |
Off-site 86.205.18.20 (SSH -p 2222) |
Same stack; WAN peer ports not open yet — runs standalone until firewall work lands. |
Public gateway (operator-owned): TLS +
https://massa-ai.freeboxos.fr/explorer/
proxies /explorer/v1/* to a LAN indexer.
Point the stack at an already-running Massa node (node keys stay on the host;
the indexer only needs read-only public gRPC, default :33037):
docker compose build
INDEXER_NODE_GRPC_URL=http://host.docker.internal:33037 \
INDEXER_NETWORK=mainnet \
MASSA_EXPLORER_MAINNET_ENDPOINTS=http://localhost:8080 \
docker compose up -d
curl -s http://127.0.0.1:8080/v1/health | jq
# Explorer: http://127.0.0.1:8081/docker compose down # keeps the RocksDB volume
docker compose down -v # also wipes RocksDB — destructiveFor TLS / rate limits, front the indexer with
nginx/indexer.conf.sample.
Requirements: Rust 1.81.0 (see massa-indexer/rust-toolchain.toml),
Node.js 20+, a Massa node with public gRPC (:33037) built with
execution-info.
# Indexer
cd massa-indexer
CXXFLAGS="-include cstdint" cargo build --release
./target/release/massa-indexer serve --config config/indexer.toml
# Explorer
cd massa-explorer
npm ci
npm run dev # http://localhost:5173
# production:
npm run build && npm run previewProduction config templates live under massa-indexer/config/
(indexer.production.toml, indexer.mainnet.toml, …). Override secrets and
host-specific values with INDEXER_* environment variables (see
massa-indexer/src/config.rs) — especially AWS legacy credentials.
Full recipe: ARCHITECTURE.md (§5 provisioning, §6
upgrades, §8 peers, §9 legacy import). Short version:
| Path | Contents |
|---|---|
/home/damip/massahistory/ |
This git checkout (source + explorer dist/). |
/home/damip/massahistory/massa/ |
Local Massa node checkout (not in git). |
/data/indexer/rocksdb/ |
Indexer database (not in git — terabytes). |
/data/indexer/indexer.toml |
Host-local indexer config. |
/etc/default/massa-indexer-legacy |
Optional AWS creds (indexer1 only; root-readable). |
| systemd | massa-node, massa-indexer, nginx |
# on a build host
cd massa-indexer && CXXFLAGS="-include cstdint" cargo build --release
# copy binary only — never rsync rocksdb
scp target/release/massa-indexer user@host:/tmp/massa-indexer.new
ssh user@host 'sudo install -m 0755 /tmp/massa-indexer.new /usr/local/bin/massa-indexer
&& sudo systemctl restart massa-indexer'cd massa-explorer && npm ci && npm run build
# LAN nginx root (example):
rsync -az --delete --exclude config.js dist/ user@host:/home/damip/massahistory/massa-explorer/dist/
# Public /explorer/ subpath build:
npx vite build --base=/explorer/ --outDir=dist-explorer
rsync -az --delete --exclude config.js dist-explorer/ user@gateway:/var/www/massa-explorer/dist/On each host, [peer] enabled = true and [peer.peers.*] URLs pointing at
siblings’ :9443. Today indexer1 ↔ indexer2 on the LAN; indexer3 joins when
its public :9443 (and related node ports) are reachable.
| Port | Service |
|---|---|
| 31244 | massa-node consensus (P2P) |
| 31245 | massa-node bootstrap |
| 33035 | massa-node public JSON-RPC |
| 33036 | massa-node public REST v2 |
| 33037 | massa-node public gRPC (indexer → node) |
| 31248 | massa-node metrics |
| 8080 | indexer REST (usually only via nginx :80) |
| 9443 | indexer peer gRPC |
| 80 / 443 | nginx (explorer + /v1/ proxy) |
Do not expose private node APIs (33034, 33038).
- Never delete
/data/indexer/rocksdbunless you intentionally want a full resync (hours–days on mainnet). - Never commit AWS keys, node
node_privkey.key, or hostindexer.tomlwith secrets — use systemd env files. - Rolling upgrades: restart one host at a time; peers cover gaps.
cd massa-indexer && CXXFLAGS="-include cstdint" cargo test --lib --tests
cd massa-explorer && npm run lint && npm test && npm run buildGitHub Actions (.github/workflows/ci.yml) runs fmt/clippy/tests and builds
the Docker images on every push.
massa-history/
├── massa-indexer/ Rust indexer (binary + lib + tests)
├── massa-explorer/ React SPA
├── massa-proto/ Vendored .proto files
├── nginx/ Sample reverse-proxy configs
├── spec.md Functional specification
├── explanation.md Design rationale
├── ARCHITECTURE.md Physical deployment playbook
├── SECURITY.md Threat model / hardening
├── docker-compose.yml Reference two-service compose
└── .github/workflows/ CI
Dual-licensed under MIT OR Apache-2.0. See LICENSE-MIT
and LICENSE-APACHE.