Files
nfel 88e227f80c
CI/CD Stage / stage (push) Failing after 1m12s
feat(gl): add standalone ledger authority mode
2026-10-06 22:40:07 +03:30

6.9 KiB

Standalone General Ledger

GL's journal API and explorer require only GL's own PostgreSQL database. Stellar, Kuknos, Wallet, and an OpenTelemetry collector are not startup or posting dependencies. Blockchain references on journals are optional.

Run independently

From the GL directory, set two runtime secrets and launch:

export DARANO_GL_DB_PASSWORD='<database password>'
export DARANO_GL_ADMIN_TOKEN='<administrative retry token>'
docker compose -f deployment/compose.standalone.yaml up --build -d --wait

The gRPC API is on 127.0.0.1:8600; the read-only dashboard is on http://127.0.0.1:8601. Images must be built/downloaded before an outage. The runtime network is internal and has no external network access. Settlement submission and telemetry export are explicitly disabled. The settlement queue remains persisted and queryable; disabling its worker does not confirm queued blockchain transactions.

The named ledger-data volume retains journals across container restarts and ordinary docker compose down. Do not use down -v on a ledger containing financial records.

This launch creates a new, empty ledger. Before treating it as the source of truth, restore the existing authoritative GL database, or import approved, balanced opening journals and replay all subsequent committed movements. Running a second empty GL does not provide failover for the first.

What remains available during a blockchain outage

  • Atomic, balanced, append-only journal posting with idempotent retries.
  • Journal lookup, history, lifecycle events, and balances rebuilt from entries.
  • Read-only exploration of the same database.
  • Durable storage of settlement requests already carrying signed XDR.

GL readiness reflects its database. Settlement backlog or submission failures do not prevent ledger reads or writes. Database loss or failed journal commits must prevent a caller from declaring an unrecorded financial effect successful.

Financial authority versus settlement

The committed journal is the source of book balances. A blockchain hash is settlement evidence, not the journal identity. Do not modify a journal to add later confirmation; retain it as separate settlement/lifecycle evidence.

The persisted GL_ONLY operating mode makes GL the execution authority. PostInternalJournal commits balanced internal effects synchronously, checks that affected available/frozen accounts do not become negative, and serializes concurrent spending inside PostgreSQL. An identical retry returns the original journal even after the mode changes. In GL_ONLY, new effects must use the authenticated internal posting API; ordinary ingestion rejects new journals while still accepting identical retries. PostgreSQL enforces the funds checks and rejects external blockchain movements at sealing. Business admission still owns user authorization, asset eligibility, fees, and exact counterparty legs.

GL starts in NORMAL; mode is stored in its database and survives restarts. Disabling the settlement worker in TOML is separate from choosing GL_ONLY. Read the current revision, then use the authenticated SetOperatingMode API to change mode. Each change retains actor, reason, revision, and time in an immutable audit table. Stale revisions fail. Enabling GL_ONLY requires no in-flight settlement claims and no negative user account balances. Drain the Wallet outbox, pause and drain in-flight Wallet processing, and confirm the ledger includes every committed effect before switching.

The existing AdminPanel controls GL mode through its GL journals → GL operating mode page. Configure GL_ADMIN_GRPC_ADDRESS and GL_ADMIN_GRPC_TOKEN (matching GL's DARANO_GL_ADMIN_TOKEN), and grant ledgerops.set_operating_mode to designated operators. AdminPanel never updates GL tables directly; mode changes go through GL's API.

Wallet remains the public entry point in both modes: the future GL-only route must preserve the existing Wallet endpoints, response formats, and UI. In normal operation, admitted requests continue through GL settlement to Stellar/Kuknos. With GL-only routing implemented and enabled, Wallet will instead submit internal effects directly to GL and serve GL balances through those same Wallet endpoints. See the future Wallet routing contract.

That Wallet routing is not implemented yet; keep NORMAL selected for the current deployment. The existing Wallet configuration toggle still selects read-only behavior. Wallet consumers must route internal execution to PostInternalJournal; the AdminPanel change does not replace Wallet's blockchain-dependent workflows. GL keeps its own readiness healthy but reports settlement as paused, so existing normal-mode Wallet clients fail closed until their internal routes are integrated. A committed GL journal is the financial success result; Wallet state becomes an idempotent projection of it. Do not also send the same effect through the normal Wallet outbox under a different key: that would duplicate the movement. Existing normal-mode committed Wallet effects continue using their durable outbox.

Internal transfers, freezes, releases, trades, and clearing movements are represented by balanced entries with no blockchain reference. An operation that actually sends money outside the internal books cannot become externally settled during an outage. External blockchain account entries and settlement submission are rejected in GL_ONLY; store any future external intent separately and keep its settlement pending.

After blockchain recovery, reconcile journals against external evidence before resuming normal settlement. Returning to NORMAL requires an operator to set reconciliation_confirmed=true; this is an explicit assertion, not an automated proof. While GL_ONLY is enabled, the worker leaves the existing settlement queue untouched. Mode changes reject in-flight claims so a toggle cannot race an already claimed submission. The existing queue requires signed XDR; it does not turn arbitrary journals into blockchain transactions. Sequence numbers, transaction expiry, and exhausted retries may require operator review and reconstruction. Never blindly resubmit all journals or mark them settled.

Verify and preserve the ledger

The existing write/read load test can exercise a disposable standalone database without any blockchain endpoint:

go run ./cmd/gl-loadtest -duration 5s -concurrency 1 -wallets 10

This writes synthetic journals and must not run against an authoritative financial database. Restart GL and confirm the same journal IDs and balances remain queryable.

Keep PostgreSQL backups and WAL recovery independent of the blockchain services, and periodically verify a restore. For example, create a logical backup without deleting or changing ledger records:

docker compose -f deployment/compose.standalone.yaml exec -T postgres \
  pg_dump -U gl -d gl_db -Fc > gl.dump

A named volume provides persistence on one host, not disaster recovery.