Skip to content
HarmonicLabsPublic

About

Typescript implementation of cardano-node

Resources

Contributing

Stars

7 stars

Watchers

0 watching

Forks

Latest commit

 

History

178 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gerolamo

Cardano TypeScript node / relay — Bun runtime

Gerolamo is a lightweight, modular Cardano node/relay in TypeScript (Bun).
It is HarmonicLabs software — not TxPipe / @txpipe/gerolamo.

Layer What you get
N2N Handshake, ChainSync, BlockFetch → SQLite
HTTP Blocks, UTxOs, health/metrics, Mini-Blockfrost subset, tx submit
N2C Optional node.socket (LocalChainSync, LocalTxSubmit, minimal LSQ, TxMonitor)
Bootstrap Mithril via external mithril-client + chunk apply / batch hydrate

Honest limits: soft body path available; full Praos leadership / governance not in scope. Mini-BF is a subset. LSQ is minimal. Soft hydrate ≠ consensus proof.


Quick start

git clone https://github.com/HarmonicLabs/gerolamo.git
cd gerolamo
bun install          # installs the node, the desktop app and the dashboard (one command)
bun run preflight    # tells you if anything on the machine is missing

# Live preprod node
bun src/index.ts start-gerolamo --network preprod

# Desktop Control Center (Electrobun). First launch downloads the Electrobun
# Linux runtime (~150 MB) from GitHub; after that it starts offline.
bun run ui:dev

# Mainnet
bun src/index.ts start-gerolamo --network mainnet

Requirements: Bun ≥ 1.1 and, for the desktop app on Linux, WebKitGTK 4.1 + GTK 3 (bun run preflight prints the exact apt/dnf/pacman line for your distro). Everything else is in the repo: the HarmonicLabs libraries that are not on npm yet ship as tarballs in vendor/, so no sibling checkouts are needed. bun run test runs the node and desktop suites.

Surface Default
HTTP API http://127.0.0.1:3030
DB ./.live/test.db (override with --db-path or config dbPath)
Entry bun src/index.ts start-gerolamo

Flags (instance isolation)

Every setting is a flag and a config.json key; Gerolamo reads no environment variables. The full list is in docs/CONFIG.md. A flag beats config.json, which beats the default; --set key.path=value sets any config key.

Flag config.json Purpose
--network networkName preprod (default) | preview | mainnet
--config <file> - Instance config.json, merged over the network's bundled one
--db-path <file> dbPath SQLite path
--port <n> port HTTP port (default 3030)
--n2c-socket <path> n2cSocketPath Ouroboros N2C unix socket path
--no-n2c n2c.enabled Disable N2C

Config: src/config/{preprod|mainnet}/config.json
Topology: src/config/{network}/topology.json

Note: config unixSocket = HTTP over unix — not N2C. Use --n2c-socket for Ouroboros.

INST="$HOME/.local/share/myapp/gerolamo/preprod-1"
mkdir -p "$INST"/{data,logs}

bun src/index.ts start-gerolamo --network preprod \
  --db-path "$INST/data/gerolamo.db" \
  --port 3030 \
  --n2c-socket "$INST/node.socket"

One writer per DB. Unique port per live instance.


HTTP API

curl -s http://127.0.0.1:3030/health | jq .
curl -s http://127.0.0.1:3030/metrics | jq .
curl -s http://127.0.0.1:3030/governor | jq .
open http://127.0.0.1:3030/stats   # HTML dashboard (or curl -s …/stats | head)
curl -s http://127.0.0.1:3030/block/<slot-or-hash>
curl -s "http://127.0.0.1:3030/utxo/<txhash>:<index>"
curl -s http://127.0.0.1:3030/api/v0/
curl -s http://127.0.0.1:3030/api/v0/blocks/latest | jq .
curl -X POST http://127.0.0.1:3030/txsubmit --data-binary @tx.cbor \
  -H "Content-Type: application/cbor"
Method Path Notes
GET /health /healthz { healthy, network, port, uptimeSec }
GET /metrics tipSlot, utxoCount, epoch, epochNonce, peers, flags
GET /governor /api/v0/governor Peer governor snapshot { cold, warm, hot, total, hotKeys, targets }
GET /stats /dashboard HTML dashboard (local only) — tip, UTxO, epoch, peer tiers; WS-first via /ws/stats, 5s poll fallback
WS /ws/stats Bun WebSocket stream: hello + tip/peers/metrics/governor topics; client ops subscribe/unsubscribe/ping
GET /block/{slot|hash} Raw CBOR hex
GET /utxo/{txhash:index} UTxO JSON if unspent
POST /txsubmit Raw CBOR → mempool / hot peers
GET/POST /api/v0/* Mini-Blockfrost subset v0.2.0

Mini-BF v0.2.0: health, epochs/latest (+ params if stored), blocks (+ /txs), address UTxOs + transactions, tx by hash, tx UTxOs (unspent only), mempool snapshot, tx submit (returns body hash).
Tx/address history needs tx_index (empty until backfill or forward index). Not full Blockfrost. See docs/gerolamo-vs-dolos-gap.md.

# Ops stream (Bun WS)
# browser: open /stats  — or:
# bun -e 'const ws=new WebSocket("ws://127.0.0.1:3040/ws/stats"); ws.onmessage=e=>console.log(e.data)'

# Mini-BF listing
curl -s http://127.0.0.1:3040/api/v0/ | jq '{version,endpoints}'

# Off-hot-path index backfill (never dual-write soak batch.db while hydrate runs)
bun scripts/backfill-tx-index.mjs --db .live/test.db --limit 200
# refuse soak unless hydrate stopped: --force-batch

How the tip advances (ChainSync)

Yes — peer tip is carried with each header on the N2N ChainSync mini-protocol.

  1. Hot peer streams MsgRollForward = header + tip (point the peer claims is chain head).
  2. Gerolamo parses the header (headerParser / handleRollForward).
  3. BlockFetch pulls the body when needed.
  4. Soft body validation (optional) → apply → SQLite MAX(slot) rises.
  5. HTTP /metrics / /stats / Mini-BF blocks/latest read that DB tip.

So tip is not HTTP-only. Frozen tipSlot + hot = 0 means stalled (no header pipeline), not “at tip.”

Tip + peers (local)

# Local tip + peer tiers
curl -s http://127.0.0.1:3030/metrics | jq '{tip:.tipSlot, peers:.peers, utxo:.utxoCount, epoch}'
curl -s http://127.0.0.1:3030/governor | jq '{hot, warm, cold, hotKeys, warmKeys, coldSample, targets}'

# HTML dashboard (peer bars + metrics panels, no external APIs)
open http://127.0.0.1:3030/stats

Frozen tipSlot + hot = 0 = stalled (no ChainSync header pipeline).

Optional log monitor (outside the node)

# Read-only watcher for a live node log + DB (does not write chain DBs)
bun scripts/node-watch.mjs --log /tmp/gerolamo-live-test.log --db .live/test.db --http 3040 --port 3041
curl -s http://127.0.0.1:3041/health | jq .
curl -s http://127.0.0.1:3041/errors | jq .
curl -s 'http://127.0.0.1:3041/tail?n=40' | jq .
Method Path Notes
GET http://…:3041/health mon verdict, toHexCrashes, tip
GET http://…:3041/errors classified log counts + samples
GET http://…:3041/tail?n=N last N log lines
GET http://…:3041/ full JSON snapshot

Embed in your project (The Lab & others)

Agents / integrators: start with agent.md — spawn contract, Lab RPC map, density path, N2C notes, definition of done.

Two planes

Plane Job Owner
Data tip, blocks, UTxO, Mini-BF, submit Gerolamo HTTP (+ optional N2C)
Control detect, spawn, stop, PID, instance dirs, log files Host app

The Lab already implements control:

  • Service: TheLab/src/bun/gerolamoService.ts
  • Layout: ~/.local/share/thelab/gerolamo/<id>/
  • Spawn: bun src/index.ts start-gerolamo with --db-path + --port
  • Health: GET /health · Logs: instance logs/daemon.log

Recipe for any host:

  1. Point at this repo + Bun
  2. Instance dir + unique DB + unique port
  3. Spawn child process (cwd = repo)
  4. Poll /health then /metrics for tip
  5. Consume Mini-BF / block / N2C
  6. Stop with SIGTERM

Status / logs interface (product direction)

Host apps should not rely on pgrep + ad-hoc log paths alone.

Today Next (recommended)
GET /health + GET /metrics GET /api/v0/status — one dashboard blob
Host tails daemon.log Optional GET /api/v0/logs?tail=N (bounded)

Keep start/stop in the host. Gerolamo stays data + self-status.


Density / offline hydrate

Rebuild soft state from local immutable chunks (no live peers required):

# Fast path (separate DB — never dual-write a live node DB)
bun scripts/batch-hydrate.mjs --wipe --from 0 --progress 25 \
  --db .hydrate/batch.db \
  --chunks snapshots/preprod/db/immutable

bun scripts/batch-watch.mjs --once

Docs: docs/hydration.md.

Optional Mithril download (external binary; Gerolamo does not reimplement cert crypto):

bun src/index.ts mithril-bootstrap --network preprod \
  --download-dir ./snapshots/preprod

Ancillary ledger UTxO extract is blocked (A2 CBOR adapter) — density = chunks.
Research: docs/mithril-native-client-research.md.


N2C

Optional. Gerolamo does not create a node.socket on every start.

Enable when How
Flag --n2c-socket ./ledger/node.socket (config n2cSocketPath)
Config n2c.enabled: true + n2c.socketPath (path alone is not enough)
Disable --no-n2c (or omit path)
bun src/index.ts start-gerolamo --network preprod \
  --n2c-socket ./ledger/node.socket

Hosts: Handshake, LocalChainSync, LocalTxSubmit, LocalTxMonitor, minimal LocalStateQuery.
Client pattern (Lab): TheLab/src/bun/dolos/n2cClient.ts — Multiplexer node-to-client.
Plan: docs/N2C_IMPLEMENTATION_PLAN.md.

Note: config unixSocket = HTTP over unix — not N2C. Use --n2c-socket for Ouroboros.


CLI

Command Purpose
start-gerolamo Live node
read-raw-chunks <dir> Apply immutable chunks
mithril-bootstrap Download/verify via mithril-client + optional apply
load-ancillary Ancillary path (extract blocked)
bun src/index.ts --help

Architecture (high level)

bun src/index.ts start-gerolamo
        │
        ├─ init SQLite (--db-path / config dbPath)
        ├─ peerBlockServer  :3030  ── HTTP + Mini-BF
        ├─ peerManager      N2N    ── ChainSync / BlockFetch → DB
        └─ N2CServer (opt)  unix   ── LocalChainSync / Tx / LSQ*
Area Location
Entry / CLI src/index.ts, src/cli.ts
Network start src/network/index.ts
HTTP src/network/peerBlockServer.ts
Mini-BF src/api/miniBlockfrost.ts
Peers src/network/PeerClient.ts, peerManager.ts
N2C src/network/n2c/
DB / apply src/db.ts, src/consensus/
Agent embed guide agent.md

Docs map

Doc Topic
agent.md Embed Gerolamo (agents + Lab)
docs/hydration.md Batch soft density
docs/gerolamo-vs-dolos-gap.md vs Dolos / Blockfrost
docs/mithril-native-client-research.md Native Mithril options
docs/N2C_IMPLEMENTATION_PLAN.md N2C phases
docs/GEROLAMINO.md Gerolamino (browser extension node): what Gerolamo serves it over N2N-over-WebSocket
docs/HARMONIC_LIBS_RELEASE.md Release review of the HarmonicLabs libraries (cbor, plutus-data, costmodels, cardano-ledger-ts, kes, buildooor, miniprotocols): changes, breaking changes, tests, publish order
CIP/ CIP drafts: Node-to-Node over WebSocket (browser nodes, service announcement, light query, discovery)

Development

bunx tsc --noEmit
bun src/index.ts start-gerolamo --network preprod
# optional inspect
bun --inspect src/index.ts start-gerolamo --network preprod

Logs: config logs.logDirectory (often ./logs/…) JSONL when enabled.
Do not commit ledger/, .hydrate/, or secrets.

Branch for Lab work: The-Lab.

Vendored libraries (vendor/)

Some HarmonicLabs libraries are installed from tarballs in vendor/ rather than npm. Four of them are conformance builds: cardano-ledger-ts, cbor, plutus-data and cardano-costmodels-ts, each built from a branch named conformance-fixes in its own repository. They make Gerolamo's decoders match cardano-ledger exactly, for pragma-org's node-diversity-conformance scenarios (CBOR decoding 9 200 / 9 200, ledger rules 277 / 277, UPLC 1 059 / 1 059). Gerolamo's own root cbor stays 1.6.6; package.json overrides give the conformance builds only to the 2.x consumers. What changed, how they are wired and how to refresh them: vendor/README.md. Harnesses for the scenarios: scripts/ndc/, results and remaining items: docs/parity/ndc-*.md.


Roadmap (blunt)

Have: N2N sync, SQLite soft ledger, HTTP + Mini-BF subset, N2C scaffold, batch hydrate, pure-TS KES.

Next for data-node parity: tx/address indexes → richer Mini-BF; canonical /api/v0/status (+ optional logs); Lab polish; Mithril WASM verify spike; LSQ breadth only if product needs it.


Resources

Built by Harmonic Labs.

About

Typescript implementation of cardano-node

Resources

Contributing

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages