⚠️ WARNING: Dingo is under heavy active development and is not yet ready for production use. It should only be used on testnets (preview, preprod) and devnets. Do not use Dingo on mainnet with real funds.
A high-performance Cardano blockchain node implementation in Go by Blink Labs. Dingo provides:
- Full chain synchronization and validation via Ouroboros consensus protocol
- UTxO tracking with 41 UTXO validation rules and Plutus V1/V2/V3 smart contract execution
- Block production with VRF leader election and stake snapshots
- Multi-peer chain selection with density comparison and VRF tie-breaking
- Client connectivity for wallets and applications
- Pluggable storage backends (Badger, SQLite, GCS, S3, PostgreSQL, MySQL)
- Tiered storage modes ("core" for consensus, "api" for full indexing)
- Peer governance with dynamic peer selection, ledger peers, and topology support
- Chain rollback support for handling forks with automatic state restoration
- Fast bootstrapping via built-in Mithril client
- Optional Midnight event indexing and MidnightState gRPC service
- Multiple external interfaces: general-purpose APIs (UTxO RPC, Blockfrost-compatible REST, Mesh/Rosetta) plus Bark for Dingo-to-Dingo C2 and archive services
Note: On Windows systems, named pipes are used instead of Unix sockets for node-to-client communication.
Start with the Dingo documentation index. It maps the
versioned documentation in this repository, the package comments exposed by
go doc, and the public operator guides at
docs.blinklabs.io.
For code-level questions, use the Go code reference
to find package doc.go files and commands that render documentation from the
exact revision checked out. Automated tools can also use the public
LLM documentation index or the focused
Cardano nodes and operations set.
Dingo supports configuration via a YAML config file (dingo.yaml), environment variables, and command-line flags. Priority: CLI flags > environment variables > YAML config > defaults.
A sample configuration file is provided at dingo.yaml.example. You can copy and edit this file to configure Dingo for your local or production environment.
The following environment variables modify Dingo's behavior:
CARDANO_BIND_ADDR- IP address to bind for listening (default:
0.0.0.0)
- IP address to bind for listening (default:
CARDANO_CONFIG- Full path to the Cardano node configuration (default:
./config/cardano/preview/config.json) - Use your own configuration files for different networks
- Genesis configuration files are read from the same directory by default
- Full path to the Cardano node configuration (default:
CARDANO_DATABASE_PATH- A directory which contains the ledger database files (default:
.dingo) - This is the location for persistent data storage for the ledger
- A directory which contains the ledger database files (default:
CARDANO_INTERSECT_TIP- Ignore prior chain history and start from current position (default:
false) - This is experimental and will likely break... use with caution
- Ignore prior chain history and start from current position (default:
CARDANO_METRICS_PORT- TCP port to bind for listening for Prometheus metrics (default:
12798)
- TCP port to bind for listening for Prometheus metrics (default:
CARDANO_NETWORK- Named Cardano network (default:
preview)
- Named Cardano network (default:
CARDANO_PRIVATE_BIND_ADDR- IP address to bind for listening for Ouroboros NtC (default:
127.0.0.1)
- IP address to bind for listening for Ouroboros NtC (default:
CARDANO_PRIVATE_PORT- TCP port to bind for listening for Ouroboros NtC (default:
3002)
- TCP port to bind for listening for Ouroboros NtC (default:
CARDANO_RELAY_PORT- TCP port to bind for listening for Ouroboros NtN (default:
3001)
- TCP port to bind for listening for Ouroboros NtN (default:
CARDANO_SOCKET_PATH- UNIX socket path for listening (default:
dingo.socket) - This socket speaks Ouroboros NtC and is used by client software
- UNIX socket path for listening (default:
CARDANO_TOPOLOGY- Full path to the Cardano node topology (default: "")
DINGO_PLUGINS_API_UTXORPC_CONFIG_PORT- TCP port to bind for listening for UTxO RPC (default:
9090) - Compatibility alias:
DINGO_UTXORPC_PORT
- TCP port to bind for listening for UTxO RPC (default:
DINGO_PLUGINS_API_BLOCKFROST_CONFIG_PORT- TCP port for the Blockfrost-compatible REST API (default:
3000) - Compatibility alias:
DINGO_BLOCKFROST_PORT
- TCP port for the Blockfrost-compatible REST API (default:
DINGO_PLUGINS_API_MESH_CONFIG_PORT- TCP port for the Mesh (Coinbase Rosetta) API (default:
8080) - Compatibility alias:
DINGO_MESH_PORT
- TCP port for the Mesh (Coinbase Rosetta) API (default:
DINGO_BARK_PORT- TCP port for the Bark block archive API (default:
0, disabled)
- TCP port for the Bark block archive API (default:
DINGO_BARK_BASE_URL- Base URL of a remote Bark archive node used for archive fallback (default: empty, disabled)
DINGO_BARK_BLOCK_DOWNLOAD_HOSTS- Comma-separated HTTPS hostnames additionally allowed for Bark-supplied
block download URLs. The allowlist always includes the
DINGO_BARK_BASE_URLhostname.
- Comma-separated HTTPS hostnames additionally allowed for Bark-supplied
block download URLs. The allowlist always includes the
DINGO_BARK_CLIENT_CA_FILE_PATH- PEM CA bundle used to authenticate every Bark DatabaseService caller.
DINGO_BARK_OPERATOR_CERTIFICATE_FINGERPRINTS- Comma-separated SHA-256 client certificate fingerprints authorized for destructive Bark DatabaseService RPCs.
DINGO_HEALTH_PORT- TCP port for the liveness/readiness probe listener (default:
12799,0disables). BindsbindAddr, like the relay and metrics listeners.
- TCP port for the liveness/readiness probe listener (default:
DINGO_HEALTH_READY_GAP_SLOTS- Slots the chain tip may trail the wall-clock slot while
/readyzstill reports ready (default:1000)
- Slots the chain tip may trail the wall-clock slot while
DINGO_DEBUG_BIND_ADDR- IP address to bind for unauthenticated pprof endpoints (default:
127.0.0.1) - This is independent of the public and private node bind addresses; set a wildcard address only when an external network control protects it
- IP address to bind for unauthenticated pprof endpoints (default:
DINGO_DEBUG_PORT- TCP port for pprof endpoints (default:
0, disabled)
- TCP port for pprof endpoints (default:
DINGO_HISTORY_EXPIRY_ENABLED- Enable local expiry of immutable block CBOR older than the ledger stability
window (default:
false)
- Enable local expiry of immutable block CBOR older than the ledger stability
window (default:
DINGO_HISTORY_EXPIRY_FREQUENCY- How often a history-expiry node scans for old local blocks (default:
1h)
- How often a history-expiry node scans for old local blocks (default:
DINGO_STORAGE_MODE- Storage mode:
core(default) orapi corestores only consensus data (UTxOs, certs, pools, protocol params)apiadditionally stores witnesses, scripts, datums, redeemers, and tx metadata- API servers (Blockfrost, UTxO RPC, Mesh) require
apimode
- Storage mode:
DINGO_RUN_MODE- Application-wide operational mode for a bare
dingoinvocation:serve(default),load,dev, orleios - Explicit subcommands select their own effective operation; for example,
dingo syncruns in sync mode regardless of the configured value - Relay, producer, storage, API, and validation settings do not select a run mode
- Application-wide operational mode for a bare
DINGO_START_ERA- Experimental startup era override. Set to
dijkstraonly for Dijkstra/Leios test networks; leave empty to follow genesis protocol version.
- Experimental startup era override. Set to
DINGO_LOGGING_FORMAT- Log output format:
text(default, human-readable) orjson(machine-parseable, for ELK/Loki ingestion)
- Log output format:
DINGO_LOGGING_LEVEL- Minimum log level:
debug,info(default),warn, orerror(the--debugflag overrides this todebug)
- Minimum log level:
DINGO_MIDNIGHT_ENABLED- Enable Midnight indexing in API storage mode (default:
false)
- Enable Midnight indexing in API storage mode (default:
DINGO_MIDNIGHT_SERVER_ENABLED- Independently enable the Midnight gRPC server in API storage mode
(default:
false)
- Independently enable the Midnight gRPC server in API storage mode
(default:
DINGO_MIDNIGHT_REFLECTION_ENABLED- Enable gRPC reflection on the Midnight server; requires the server
(default:
false)
- Enable gRPC reflection on the Midnight server; requires the server
(default:
DINGO_MIDNIGHT_HOST- Midnight gRPC listen address (default:
127.0.0.1)
- Midnight gRPC listen address (default:
DINGO_MIDNIGHT_PORT- Midnight gRPC listen port; must be non-zero when the server is enabled
(default:
50051)
- Midnight gRPC listen port; must be non-zero when the server is enabled
(default:
TLS_CERT_FILE_PATH- TLS certificate used directly by an enabled Midnight gRPC listener and as the built-in UTxO RPC compatibility default; requiresTLS_KEY_FILE_PATH(default: empty)TLS_KEY_FILE_PATH- matching TLS private key for those listeners (default: empty)
To run Dingo as a stake pool operator producing blocks:
CARDANO_BLOCK_PRODUCER- Enable block production (default:false)CARDANO_SHELLEY_VRF_KEY- Path to VRF signing key fileCARDANO_SHELLEY_KES_KEY- Path to KES signing key fileCARDANO_SHELLEY_OPERATIONAL_CERTIFICATE- Path to operational certificate file
Dingo block production is exercised by the all-Dingo DevNet and has produced blocks on preview and preprod. Current releases do not support mainnet operation.
# Preview network (default)
./dingo
# Preprod
CARDANO_NETWORK=preprod ./dingo
# Or with explicit config path
CARDANO_NETWORK=preprod CARDANO_CONFIG=path/to/preprod/config.json ./dingoDingo creates a dingo.socket file that speaks Ouroboros node-to-client and is compatible with cardano-cli, adder, kupo, and other Cardano client tools.
Cardano configuration files are bundled in the Docker image. For local builds, you can find them at docker-cardano-configs.
Dingo's resource needs depend on the network, storage mode, and whether it is
bootstrapping or serving near the chain tip. The following measurements were
captured on September 4, 2026 from nine process samples at 30-second intervals
(about four minutes) on otherwise shared 32 GiB hosts. CPU values below are
derived from the recorded process percentages (%CPU / 100) and represent
core-equivalents, not a new sampling run. They are observations, not capacity
guarantees; the mainnet sample was on released v0.70.5, while the Preprod
and Preview samples used an origin/main build.
| Network and mode | Average CPU cores | Peak CPU cores | Average RSS | Peak RSS |
|---|---|---|---|---|
Mainnet, core |
0.863 | 0.864 | 4.3 GiB | 4.5 GiB |
Preprod, core |
0.545 | 0.547 | 1.5 GiB | 1.5 GiB |
Preview, core |
0.095 | 0.096 | 1.3 GiB | 1.4 GiB |
For a practical starting point, provision at least 2 vCPUs and 8 GiB RAM for
a core node. This leaves CPU and memory headroom above the observed mainnet
serve sample and allows for variation from peer activity, compaction, upgrades,
and catch-up. A node that serves APIs or uses storageMode: api should use at
least 4 vCPUs and 16 GiB RAM until longer production-like measurements are
available. Disk capacity must also cover the selected storage mode and its
temporary bootstrap peak.
API-mode Mithril bootstrap is a separate workload and needs more resources than
near-tip serving. An in-progress Preview sample averaged 0.932 CPU cores
(0.972 peak, derived from the recorded percentages) and grew from 0.3 GiB to
3.0 GiB RSS in about four minutes while importing a snapshot; the
run had reached 1.8% ledger/UTxO import, so its eventual peak was not measured.
The same run used about 17.9 GB of host /data at the time of sampling. Treat
these API bootstrap values as preliminary and leave substantial additional CPU,
RAM, and disk headroom until a complete bootstrap measurement is available.
The following complete Mithril API-mode runs used Dingo origin/main at
commit 034c12e6. The Preprod backfill was followed by a deferred
critical-index rebuild; the overall duration includes both phases. These are
environment-specific planning baselines, not capacity guarantees.
| Network | Dingo ref | Storage mode | Backfill | Deferred index rebuild | Overall | Blocks / transactions |
|---|---|---|---|---|---|---|
| Preprod | origin/main (034c12e6) |
api |
6h09m06s | 12m30.607s | ~6h21m37.7s | 5,169,107 / 6,795,854 |
| Preview | origin/main (034c12e6) |
api |
7h38m34s | not separately recorded | 7h38m34s | 4,658,378 / 6,890,796 |
The API-mode runs reached approximately 8.1 GiB RSS on Preprod and 9.6 GiB RSS on Preview at their highest observed checkpoints. Peak CPU was about 1.29 core-equivalents on both runs; swap reached about 145 MiB on Preprod and 125 MiB on Preview. The observed data footprints were approximately 60 GiB and 49 GiB respectively. These resource readings were captured during active bootstrap and should not be used as steady-state serving requirements; leave additional headroom for host I/O, memory pressure, and future network growth.
# Run on preview (default)
docker run -p 3001:3001 ghcr.io/blinklabs-io/dingo
# Run on preprod with persistent storage
docker run -p 3001:3001 \
-e CARDANO_NETWORK=preprod \
-v dingo-data:/data/db \
-v dingo-ipc:/ipc \
ghcr.io/blinklabs-io/dingoThe image is based on Debian bookworm-slim and includes cardano-cli, nview, and txtop. Mithril snapshot support is built into dingo natively (dingo mithril sync). The Dockerfile sets CARDANO_DATABASE_PATH=/data/db and CARDANO_SOCKET_PATH=/ipc/dingo.socket, overriding the local defaults of .dingo and dingo.socket — the volume mounts above map to these container paths.
| Port | Service | Default |
|---|---|---|
| 3001 | Ouroboros NtN (node-to-node) | Enabled |
| 3002 | Ouroboros NtC over TCP | Enabled |
| 12798 | Prometheus metrics | Enabled |
| 12799 | Health probes (/health, /healthz, /readyz) |
Enabled |
| 3000 | Blockfrost REST API | Disabled |
| 8080 | Mesh (Rosetta) REST API | Disabled |
| 9090 | UTxO RPC (gRPC) | Disabled |
| 50051 | Midnight state (gRPC) | Disabled |
| — | Bark archive (gRPC) | Disabled (example when enabled: 9091) |
| — | pprof debug endpoints | Disabled (DINGO_DEBUG_PORT=0; loopback when enabled) |
Dingo serves liveness and readiness on a listener of its own
(healthPort, default 12799), separate from Prometheus metrics, pprof,
and every API listener. It starts in both storage modes and whether or not
the Blockfrost, Mesh, and UTxO RPC APIs are enabled, so the probe is
available in the default core relay configuration that the shipped
docker-compose.yml runs.
dingo mithril sync serves it too. A Mithril bootstrap runs as its own
process before the node starts, takes hours on mainnet, and reports live and
not-ready throughout, so a container HEALTHCHECK pointed at healthPort
keeps passing while the snapshot downloads instead of replacing the container
partway through it.
| Path | Meaning | 200 when | Non-200 when |
|---|---|---|---|
/healthz (and /health) |
Liveness | The process is up and the listener is serving | Never, while the process can answer |
/readyz |
Readiness | The chain tip is within healthReadyGapSlots (default 1000) of the wall-clock slot |
Starting up, bootstrapping from Mithril, catching up, or the tip has frozen |
Both return JSON carrying the readiness verdict, a reason, and the observed tip gap in slots:
{"live":true,"ready":false,"reason":"tip gap 4211 slots exceeds tolerance of 1000 slots","tipGapSlots":4211,"status":"unhealthy"}The split is deliberate, because an orchestrator acts on the two
differently. Docker, Swarm, and ECS replace an unhealthy container, and
Kubernetes restarts a container whose livenessProbe fails; a node doing an
initial sync is legitimately not useful for hours or days, and none of the
conditions that freeze a tip are repaired by a restart loop. So liveness
stays independent of sync state and is what the image's HEALTHCHECK
probes; that check follows DINGO_HEALTH_PORT and reports healthy without
probing when it is 0. Readiness is the signal that catches a frozen tip, and failing it
removes a pod from a Service or a target from a load balancer without
killing the node.
Kubernetes:
livenessProbe:
httpGet: { path: /healthz, port: 12799 }
periodSeconds: 30
readinessProbe:
httpGet: { path: /readyz, port: 12799 }
periodSeconds: 15The readiness tolerance matches forgeStaleGapThresholdSlots: both answer
"has this node stopped following the chain?", and a probe that flapped more
readily than the forger's own staleness gate would evict a node the forger
still considers current. Raise healthReadyGapSlots on a network with a
lower active slot coefficient, or lower it to detect a stall sooner at the
cost of flapping on quiet stretches of chain.
The tip gap is read from the ledger's slot clock — the same value the
dingo_tip_gap_slots gauge exports — so readiness does not depend on the
Prometheus listener.
Dingo has two storage modes commonly used in three node configurations:
| Node configuration | Settings | Current behavior |
|---|---|---|
| Relay | storageMode: core, blockProducer: false |
Validates and follows the chain, participates in NtN/NtC, relays blocks and transactions, and stores consensus state without API history |
| Block producer | storageMode: core, blockProducer: true plus VRF/KES/opcert paths |
Includes the relay behavior, leader election, block forging, forged-block self-validation, and block diffusion |
| Data/API node | storageMode: api, blockProducer: false |
Stores consensus state plus transaction, witness, script, datum, redeemer, governance, and metadata history; starts configured Blockfrost, Mesh, and UTxO RPC providers |
core is the default and smallest storage/runtime surface. The producer
profile adds forging and key operations to it. API mode adds historical
indexing and query services; it is not a separate consensus implementation.
# Relay or block producer (default)
./dingo
# API node
DINGO_STORAGE_MODE=api ./dingoOr in dingo.yaml:
storageMode: "api"Dingo includes three general-purpose external APIs, an Acropolis-compatible
Midnight state service, and Bark. UTxO RPC, Blockfrost, and Mesh are
client-facing APIs and require storageMode: "api".
Their built-in providers are registered with the instance-owned plugin host,
start on their provider defaults in API mode, and can be configured
independently under plugins.api. Set an individual port to 0 to disable that
interface.
Midnight indexing and serving are separate opt-ins. midnight.enabled starts
the indexer, while midnight.serverEnabled starts the gRPC listener for rows
already present in the Midnight tables; either may be enabled independently in
API storage mode. The listener defaults to 127.0.0.1:50051. Reflection is
available only with midnight.reflectionEnabled. Configuring both
tlsCertFilePath and tlsKeyFilePath enables TLS; plaintext is also
supported. Dingo does not add authentication to this
Acropolis-compatible service.
Bark is Dingo's own Dingo-to-Dingo archive protocol rather than an application
API. It is configured separately with barkPort and barkBaseUrl.
For public client access, the API listeners may be exposed directly or placed behind a reverse proxy or API gateway. UTxO RPC, Blockfrost, and Mesh support optional in-process TLS and accept requests without credentials.
The shorter DINGO_UTXORPC_PORT, DINGO_BLOCKFROST_PORT, and
DINGO_MESH_PORT names remain supported for compatibility. If both a
compatibility name and its plugin-form name are set, the plugin-form value
takes precedence.
| Interface | Port Env Var | Default | Protocol | Role |
|---|---|---|---|---|
| UTxO RPC | DINGO_PLUGINS_API_UTXORPC_CONFIG_PORT |
9090 | gRPC | General-purpose client API (v1alpha and v1beta) |
| Blockfrost | DINGO_PLUGINS_API_BLOCKFROST_CONFIG_PORT |
3000 | REST | General-purpose client API |
| Mesh (Rosetta) | DINGO_PLUGINS_API_MESH_CONFIG_PORT |
8080 | REST | General-purpose client API |
| Midnight | DINGO_MIDNIGHT_PORT |
50051 (server off) | gRPC | Acropolis-compatible Midnight state API |
| Bark | DINGO_BARK_PORT |
disabled | Connect/gRPC | Dingo-to-Dingo C2/archive protocol |
# Enable Blockfrost API on port 3100 and UTxO RPC on port 9090
DINGO_STORAGE_MODE=api \
DINGO_PLUGINS_API_BLOCKFROST_CONFIG_PORT=3100 \
DINGO_PLUGINS_API_UTXORPC_CONFIG_PORT=9090 \
./dingoOr in dingo.yaml:
storageMode: "api"
plugins:
api:
blockfrost: {provider: builtin, config: {port: 3100}}
utxorpc: {provider: builtin, config: {port: 9090}}api.tls sets a shared default TLS policy for every selected
plugins.api.* provider (Blockfrost, Mesh, UTxO RPC). Each field resolves
independently: plugins.api.<name>.config.tls overrides a field for that
provider only, otherwise the shared value is used and an unset policy remains
plaintext. With mode: server, both certificate and key paths are required
before the listener binds; TLS remains optional. Explicit mode: disabled
keeps a provider plaintext even when a shared TLS policy is configured. CLI and
environment values for the shared TLS policy retain the normal
CLI > environment > YAML > default precedence.
api:
tls:
mode: server
certFilePath: /run/secrets/api.crt
keyFilePath: /run/secrets/api.key
plugins:
api:
# Inherits TLS from api.tls above.
utxorpc:
provider: builtin
config:
port: 9090
mesh:
provider: builtin
config:
port: 8080
# Overrides just the certificate/key for this provider; the inherited
# api.tls.mode ("server") still applies.
blockfrost:
provider: builtin
config:
port: 3000
tls:
certFilePath: /run/secrets/blockfrost.crt
keyFilePath: /run/secrets/blockfrost.keyAPI routes require no credentials. TLS certificate/key file contents are never written to logs, error messages, or effective-config output.
The pre-existing root tlsCertFilePath/tlsKeyFilePath fields remain
supported compatibility inputs: Midnight uses the pair directly when its
listener is enabled, and UTxO RPC merges it as the
lowest-priority policy, field by field, alongside the shared api.tls default
and its own tls config — not as an all-or-nothing fallback that applies only
when both newer scopes are completely unset. For example, if shared api.tls
sets only mode: server with no certFilePath/keyFilePath, UTxO RPC still
inherits the two paths from the legacy root settings. The root pair is not
promoted onto Blockfrost or Mesh, since doing so would silently switch a
previously plaintext listener to TLS on upgrade. bindAddr and
corsAllowedOrigins are unrelated to this policy and remain root-level
settings shared by all listeners (bindAddr is also used by the relay/NtN
listener, not just the APIs). API listeners use bindAddr, whose default is
0.0.0.0; CORS remains operator-chosen through corsAllowedOrigins.
Dingo can expire immutable block CBOR from a local blob store once blocks are older than the ledger-derived stability window. This History Expiry mode is a valid standalone operational mode: without an archive fallback, reads for expired blocks return a clear history-expired error. When paired with Bark, expired or missing historical block reads can be transparently served from a remote archive node.
An archive node uses a signed-URL-capable blob plugin (s3 or gcs) and
enables Bark with barkPort. Bark answers Dingo-to-Dingo archive requests by
returning a signed object-storage URL plus block metadata. Badger is valid for a
normal local blob store, but it does not provide signed URLs and should not be
used as the Bark archive backend.
For local source builds, the s3 and gcs blob plugins require
-tags dingo_extra_plugins or make build. Official release binaries include
the extra plugin tag.
storageMode: "core"
plugins:
storage:
blob:
provider: s3
config:
bucket: "dingo-archive"
region: "us-east-1"
prefix: "preview"
barkPort: 9091A history-expiry node keeps its normal local blob store and enables
historyExpiry. Dingo expires blocks older than the ledger-derived stability
window while keeping local indexes and metadata, so reads fail explicitly as
expired history unless an archive wrapper can serve them.
storageMode: "core"
plugins:
storage:
blob:
provider: badger
config: {}
historyExpiry:
enabled: true
frequency: 1hAdd barkBaseUrl when expired historical reads should fall back to a Bark
archive:
barkBaseUrl: "http://archive.example.internal:9091"
barkBlockDownloadHosts:
- "dingo-archive.s3.us-east-1.amazonaws.com"Bark archive RPC may use the configured barkBaseUrl, but the block download
URLs returned by that service must be HTTPS, must not contain credentials, and
must match either the barkBaseUrl hostname or barkBlockDownloadHosts.
The runnable demonstration in internal/test/archive-demo/ brings up an S3
compatible Minio archive node, a local Badger history-expiry node, and an end-to-end
BlockFetch check through Bark.
Relay node (consensus only, no APIs):
./dingoAPI / data node (full indexing, one or more APIs):
DINGO_STORAGE_MODE=api DINGO_PLUGINS_API_BLOCKFROST_CONFIG_PORT=3100 ./dingoArchive node (cloud object storage plus Bark archive service):
DINGO_PLUGINS_STORAGE_BLOB_PROVIDER=s3 DINGO_BARK_PORT=9091 ./dingoHistory-expiry node (local storage plus a remote Bark archive):
DINGO_HISTORY_EXPIRY_ENABLED=true \
DINGO_BARK_BASE_URL=http://archive.example.internal:9091 ./dingoBlock producer (consensus only, with SPO keys):
CARDANO_BLOCK_PRODUCER=true \
CARDANO_SHELLEY_VRF_KEY=/keys/vrf.skey \
CARDANO_SHELLEY_KES_KEY=/keys/kes.skey \
CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE=/keys/opcert.cert \
./dingoWhen storageMode=core, the Badger blob store defaults to mmap-only settings: block-cache-size=0, index-cache-size=0, and compression=false. When storageMode=api, the default Badger profile is block-cache-size=268435456, index-cache-size=0, and compression=true. The plugins.storage.blob.config Badger settings (YAML or the matching DINGO_PLUGINS_STORAGE_BLOB_CONFIG_* environment variables) override those defaults only when explicitly set.
See dingo.yaml.example for the full set of configuration options.
Instead of syncing from genesis (which can take days on mainnet), you can bootstrap Dingo using a Mithril snapshot. Dingo has a built-in Mithril client that handles download, extraction, and import automatically. This is the fastest way to get a node running.
# Bootstrap from Mithril and start syncing
./dingo -n preview sync --mithril
# Then start the node
./dingo -n preview serveOr use the subcommand form for more control:
# List available snapshots
./dingo -n preview mithril list
# Show snapshot details
./dingo -n preview mithril show <hash>
# Download and import
./dingo -n preview mithril syncFor reproducible fresh-bootstrap comparisons, pin an exact artifact with
--mithril-pinned-digest <identity> or DINGO_MITHRIL_PINNED_DIGEST. The
identity is a snapshot digest for the v1 backend and a Cardano database
artifact hash for v2. The pin is rejected for catch-up runs and cannot override
the artifact recorded by an interrupted import.
The Docker entrypoint manages both a first-run or resumed Mithril sync and the
subsequent serve process as direct children. It forwards SIGINT and SIGTERM
to whichever child is active, waits for that child to finish, and returns the
child's exit status instead of masking an interrupted bootstrap as success.
The default v2 backend restores incremental per-immutable-file archives only
after checking the genesis-rooted certificate chain, certified Merkle root, and
each immutable-file digest. It also requires the ancillary archive: its
ledger-state and in-progress immutable files are checked against the
manifest separately signed by the ancillary key. That signature authenticates
that payload; it is not a stake certificate and does not validate the volatile
blocks after the certified immutable point.
The legacy v1 full-snapshot backend is available for inspection and
unverified library workflows, but it cannot be used for a verified fast
bootstrap because it has no signed ancillary-state boundary. The mithril list
and mithril show subcommands follow the configured backend.
This imports:
- All blocks from genesis (stored in blob store for serving peers)
- Current UTxO set, stake accounts, pool registrations, DRep registrations
- Stake snapshots (mark/set/go) for leader election
- Protocol parameters, governance state, treasury/reserves
- Complete epoch history for slot-to-time calculations
Individual transaction records, certificate history, witness/script/datum storage, and governance vote records for blocks before the snapshot are not stored by the snapshot itself. In core mode these are not needed — consensus, block production, and serving blocks to peers work without them, and new blocks processed after bootstrap will have full metadata. In api mode, dingo mithril sync automatically runs a backfill step after loading the snapshot to populate this historical data, so API servers (Blockfrost, UTxO RPC, Mesh) have complete records from genesis.
Dingo supports two working startup paths:
- A normal chain sync builds ledger and database state from downloaded blocks.
The default configuration validates historical blocks from origin and fails
closed when required ledger/UTxO state is missing. Operators intentionally
using a non-genesis intersection without complete pre-intersect state must
explicitly set
validateHistorical: falseandstrictUtxoValidation: false. - Mithril sync verifies the certificate chain and snapshot artifact, imports the separately ancillary-key-signed ledger state, stores certified immutable blocks, and strictly processes the gap between the imported state and immutable tip. Normal strict validation resumes at the imported point for the gap and all subsequently received network data. In API mode it then backfills historical query records before the APIs are used.
Performance (preview network, ~4M blocks):
| Phase | core mode | api mode |
|---|---|---|
| Download snapshot (~2.6 GB) | ~1-2 min | ~1-2 min |
| Extract + download ancillary | ~1 min | ~1 min |
| Import ledger state (UTxOs, accounts, pools, DReps, epochs) | ~12 min | ~12 min |
| Load blocks into blob store | ~36 min | ~36 min |
| Backfill historical metadata | — | ~varies |
| Total | ~50 min | ~50 min + backfill |
The following timings were measured during profiled core-mode validation
runs on 2026-08-26 and 2026-08-28, from bootstrap start through completion:
| Network | Snapshot ready | Bootstrap complete |
|---|---|---|
| mainnet | 41m 51s | 9h 10m 56s |
| preprod | 4m 07s | 37m 56s |
| preview | 12m 11s | 46m 22s |
Mainnet's total includes its index rebuild; subsequent restarts reused the completed database rather than repeating the bootstrap.
A profiled Preview api-mode run completed snapshot bootstrap and historical
metadata backfill with these timings:
| Phase | Duration |
|---|---|
| Mithril snapshot bootstrap | 39m 13s |
| Historical metadata backfill | 19h 57m 46s |
| Post-backfill index finalization | ~16m 30s |
| Total through bootstrap completion | ~20h 53m 19s |
The finalization phase is approximate; the total is the end-to-end measurement and should not be reconstructed by summing the rounded phase durations.
The API-mode measurement was taken on 2026-08-30/31 against approximately 4.6M Preview blocks. The backfill processed 6.86M transactions at roughly 64 blocks per second.
The Preview API path was also measured in a profiled run on 2026-08-31/09-01 using the SQLite bulk-load pragmas and a temporary Mithril artifact cache. It completed in 7h 36m 07s end-to-end, including the historical metadata backfill (24,547s) and deferred index rebuild (16m). The earlier Preview API baseline was approximately 20h 53m, so this run used 63.6% less elapsed time. The approximately 30 GB Mithril cache is temporary and can be removed after the snapshot is imported. Peak bootstrap space was approximately 76 GB while the cache was present (46 GB database plus 30 GB cache); after cleanup, the database requires approximately 46 GB and a fresh bootstrap needs approximately 61 GB for the database plus the 15 GB snapshot.
Bootstrapping requires temporary disk space for both the downloaded snapshot and the Dingo database:
| Network | Snapshot Size | Dingo DB | Total Needed |
|---|---|---|---|
| mainnet | ~180 GB | ~200+ GB | ~400 GB |
| preprod | ~60 GB | ~80 GB | ~150 GB |
| preview | ~15 GB | ~25 GB | ~50 GB |
| preview (API mode) | ~15 GB | ~46 GB | ~61 GB minimum (~76 GB peak during bootstrap) |
These are approximate values that grow over time. The snapshot can be deleted after import, but you need sufficient space for both during the load process.
dingo database provides offline snapshot, restore, and truncate operations.
Each subcommand operates directly against the configured data directory and
must not be run while a dingo node process has that directory open. All three
honor SIGINT and SIGTERM so an interrupt unwinds cleanly instead of leaving a
partial result behind.
# Capture a point-in-time snapshot. --dir must not already exist.
./dingo database snapshot --dir /backups/dingo-preview-2026-08-05
# Restore the configured data directory from a snapshot directory.
./dingo database restore /backups/dingo-preview-2026-08-05
# Rewind to a target point. Pass exactly one of --slot, --hash, --block-number.
./dingo database truncate --slot 12345678Truncate makes the target block the new chain tip and removes every block and metadata row added after it. Unlike a normal chain rollback it does not reject a target beyond the security parameter, because it exists for disaster-recovery scenarios (see CIP-0135) where the chain must be rewound further than Ouroboros Praos allows. The resulting database is resync-ready from the target point.
The same operations are also exposed remotely through Bark's
DatabaseService. Every DatabaseService RPC requires a client certificate
verified against barkClientCaFilePath; destructive RPCs also require the
certificate's SHA-256 fingerprint in
barkOperatorCertificateFingerprints. Bark's read-only ArchiveService
remains public on the same listener, so expose the Bark port only to the
intended network.
Dingo supports pluggable storage backends for both blob storage (blocks, transactions) and metadata storage. This allows you to choose the best storage solution for your use case.
For local source builds, badger, sqlite, the default mempool, and all three
built-in API providers are always available. GCS and S3 require
-tags dingo_extra_plugins or an official release binary. The same tag adds
the operational PostgreSQL and MySQL metadata providers, backed by the shared
database/sql store and v1alpha1 schema.
Blob Storage Plugins:
badger- BadgerDB local key-value store (default)gcs- Google Cloud Storage blob stores3- AWS S3 blob store
Metadata Storage Plugins:
sqlite- SQLite relational database (default)postgres- PostgreSQL metadata store (requiresdingo_extra_plugins)mysql- MySQL metadata store (requiresdingo_extra_plugins)
Mempool Plugins:
fifo- First-in, first-out transaction pool (default)dag- Dependency-graph transaction pool that makes transaction dependencies explicit. Ledger validation remains the source of truth for both providers; the DAG backend changes ordering and selection, not validation.
API Plugins:
blockfrost- Blockfrost-compatible REST APImesh- Mesh (Coinbase Rosetta) REST APIutxorpc- UTxO RPC gRPC API (serves both v1alpha and v1beta)
Badger value-log GC runs every five minutes at a 0.5 discard ratio by default.
Operators may set gc: false for a controlled bulk load, but should re-enable
GC for steady-state operation. GC activity and measurement guidance are
documented in docs/badger-gc.md.
Plugins can be selected via command-line flags, environment variables, or configuration file:
# Command line
./dingo --blob gcs --metadata sqlite
# Environment variables
DINGO_PLUGINS_STORAGE_BLOB_PROVIDER=gcs
DINGO_PLUGINS_STORAGE_METADATA_PROVIDER=sqlite
# Configuration file (dingo.yaml)
plugins:
storage:
blob:
provider: gcs
config:
bucket: my-cardano-blocks
metadata:
provider: sqlite
config: {}Each capability has exactly one selected provider. Provider configuration is
strictly decoded; unknown fields fail startup. Generic environment variables
flatten the capability and config path, for example
DINGO_PLUGINS_MEMPOOL_CONFIG_CAPACITY and
DINGO_PLUGINS_API_UTXORPC_CONFIG_PORT. See dingo.yaml.example.
CARDANO_DATABASE_PATH (or databasePath / --data-dir) remains a shortcut
that supplies the data directory to both local storage providers. Set
dataDir on either local provider when blob and metadata storage need separate
paths; the provider value overrides the shared shortcut.
BadgerDB Options:
dataDir- Badger data directory (defaults to the shared database path)blockCacheSize- Block cache size in bytesindexCacheSize- Index cache size in bytescompression- Enable ZSTD compressiongc- Enable garbage collection
Leave mode-sensitive Badger settings unset to use storage-mode defaults.
Google Cloud Storage Options:
bucket- GCS bucket name
AWS S3 Options:
endpoint- Optional custom S3-compatible endpointbucket- S3 bucket nameregion- AWS regionprefix- Path prefix within buckettimeout- Request timeout
S3 credentials use the standard AWS credential chain.
SQLite Options:
dataDir- SQLite data directory (defaults to the shared database path)maxConnections- Maximum connection count
Reserved PostgreSQL Options:
host- PostgreSQL server hostnameport- PostgreSQL server portuser- Database userpassword- Database passworddatabase- Database namesslMode- PostgreSQL SSL modetimeZone- PostgreSQL time zone (default: UTC)dsn- Full PostgreSQL DSN (overrides the individual connection fields)poolMaxOpenConns- Maximum open connections (default: 100)poolMaxIdleConns- Maximum idle connections (default: 10)poolConnMaxLifetime- Maximum connection lifetime (default: 1h)
Reserved MySQL Options:
host- MySQL server hostnameport- MySQL server portuser- Database userpassword- Database passworddatabase- Database namesslMode- MySQL TLS mode (mapped totlsin the DSN)timeZone- MySQL time zone location (default: UTC)dsn- Full MySQL DSN (overrides other options when set)poolMaxOpenConns- Maximum open connections (default: 100)poolMaxIdleConns- Maximum idle connections (default: 10)poolConnMaxLifetime- Maximum connection lifetime (default: 1h)
The plugin platform replaces the earlier per-plugin CLI flags and environment
variables for storage, mempool, and API ports with the plugins.* config tree
(YAML), the generic DINGO_PLUGINS_* environment scheme, and the provider
selector flags. Every removed setting has an equivalent below; values are
unchanged, only where they are set has moved.
| Removed setting | New equivalent |
|---|---|
--mempool-capacity, CARDANO_MEMPOOL_CAPACITY |
plugins.mempool.config.capacity / DINGO_PLUGINS_MEMPOOL_CONFIG_CAPACITY |
--eviction-watermark, DINGO_MEMPOOL_EVICTION_WATERMARK |
plugins.mempool.config.evictionWatermark / DINGO_PLUGINS_MEMPOOL_CONFIG_EVICTION_WATERMARK |
--rejection-watermark, DINGO_MEMPOOL_REJECTION_WATERMARK |
plugins.mempool.config.rejectionWatermark / DINGO_PLUGINS_MEMPOOL_CONFIG_REJECTION_WATERMARK |
DINGO_DATABASE_BLOB_PLUGIN |
--blob, plugins.storage.blob.provider, or DINGO_PLUGINS_STORAGE_BLOB_PROVIDER |
DINGO_DATABASE_METADATA_PLUGIN |
--metadata, plugins.storage.metadata.provider, or DINGO_PLUGINS_STORAGE_METADATA_PROVIDER |
--blob-badger-*, DINGO_DATABASE_BLOB_BADGER_* |
plugins.storage.blob.config.* / DINGO_PLUGINS_STORAGE_BLOB_CONFIG_* |
--metadata-sqlite-*, DINGO_DATABASE_METADATA_SQLITE_* |
plugins.storage.metadata.config.* / DINGO_PLUGINS_STORAGE_METADATA_CONFIG_* |
MYSQL_* MySQL connection aliases (-tags dingo_extra_plugins) |
plugins.storage.metadata.config.* / DINGO_PLUGINS_STORAGE_METADATA_CONFIG_* |
--utxorpc-port, --blockfrost-port, --mesh-port |
plugins.api.<name>.config.port / DINGO_PLUGINS_API_<NAME>_CONFIG_PORT |
Provider config fields use lowerCamelCase in YAML; the environment form
uppercases them with underscore separators (dataDir becomes
..._CONFIG_DATA_DIR). The pre-plugin API port variables DINGO_UTXORPC_PORT,
DINGO_BLOCKFROST_PORT, and DINGO_MESH_PORT still work as compatibility
aliases, and setting an API port to 0 disables that server.
You can see all available plugins and their descriptions:
./dingo listFor information on developing custom storage plugins, see database/plugin/PLUGIN_DEVELOPMENT.md.
This checklist is a compact map of implemented feature areas. The package tests, conformance suite, DevNet, and public-network evidence provide the detailed validation record.
- Network
- UTxO RPC
- Ouroboros
- Node-to-node
- ChainSync
- BlockFetch
- TxSubmission2
- Node-to-client
- ChainSync
- LocalTxMonitor
- LocalTxSubmission
- LocalStateQuery
- Peer governor
- Topology config
- Peer churn (full PeerChurnEvent with gossip/public root churn, bootstrap events)
- Ledger peers
- Peer sharing
- Denied peers tracking
- Connection manager
- Inbound connections
- Node-to-client over TCP
- Node-to-client over UNIX socket
- Node-to-node over TCP
- Outbound connections
- Node-to-node over TCP
- Inbound connections
- Node-to-node
- Ledger
- Blocks
- Block storage
- Chain selection (density comparison, VRF tie-breaker, ChainForkEvent)
- UTxO tracking
- Protocol parameters
- Genesis validation
- Block header validation (VRF/KES/OpCert cryptographic verification)
- Certificates
- Pool registration
- Stake registration/delegation
- Account registration checks
- DRep registration
- Governance
- Transaction validation
- Phase 1 validation
- UTxO rules
- Fee validation (full fee calculation with script costs)
- Transaction size and ExUnit budget validation
- Witnesses
- Block body
- Certificates
- Delegation/pools
- Governance
- Phase 2 validation
- Plutus V1 smart contract execution
- Plutus V2 smart contract execution
- Plutus V3 smart contract execution
- Phase 1 validation
- Blocks
- Block production
- VRF leader election with stake snapshots
- Block forging with KES/OpCert signing
- Slot battle detection
- Mempool
- Accept transactions from local clients
- Distribute transactions to other nodes
- Validation of transaction on add
- Consumer tracking
- Transaction purging on chain update
- Watermark-based eviction and rejection
- Selectable backend: FIFO (default) or DAG
- Database Recovery
- Chain rollback support
- State restoration on rollback
- WAL mode for crash recovery
- Automatic rollback on transaction error
- Cross-store commit fence with durable blob sync and commit timestamps
- Partial-commit and blob-only timestamp divergence detection
- Startup chain/ledger tip reconciliation and orphaned-blob cleanup
- Recovery ordering ahead of history expiry
- Database Lifecycle
- Offline snapshot, restore, and truncate (
dingo database) - Remote operation through the Bark
DatabaseService
- Offline snapshot, restore, and truncate (
- Stake Snapshots
- Mark/Set/Go rotation at epoch boundaries
- Genesis snapshot capture
- API Servers
- UTxO RPC (gRPC), serving v1alpha and v1beta
- WIP Blockfrost-compatible REST API (required endpoint families are implemented; compatibility hardening and reward parity are ongoing)
- Mesh (Coinbase Rosetta) API
- Optional Midnight event indexer and MidnightState gRPC service
- Mithril Bootstrap
- Built-in Mithril client
- Ledger state import (UTxOs, accounts, pools, DReps, epochs)
- Block loading from ImmutableDB
Additional planned features can be found in our issue tracker and project boards.
Catalyst Fund 12 - Go Node (Dingo)
Catalyst Fund 13 - Archive Node
Check the issue tracker for known issues. Due to rapid development, bugs happen especially as there is functionality which has not yet been developed.
This requires Go 1.26 or later. You also need make.
The default target formats and builds. It does not run tests; use make test
for those.
# Format and build (default target)
make
# Build only
make build
# Run
./dingo
# Run without building a binary
go run ./cmd/dingo/make build builds every command under cmd/: the dingo node itself and
koios-parity, which compares Dingo ledger state against Koios for a given
network and epoch.
Metadata storage uses typed database/sql code generated by
sqlc from sqlc.yaml. Regenerate it with make sql after
changing a query, and make sql-check fails when the checked-in output is
stale.
make test # All tests with race detection
go test -v -race -run TestName ./package/ # Single test
make bench # Benchmarks
make bench-mempool # Compare FIFO and DAG mempools
make docs-parity # Docs agree with go.mod, Makefile, compose, Koios matrix
make sql-check # Generated sqlc output is currentDingo reports compatibility in separate layers. The ledger corpus is the
pinned Cardano Blueprint archive consumed from ouroboros-mock; a green ledger
result is not complete node conformance.
| Profile | Command | Current scope |
|---|---|---|
| Ledger rules | go test ./internal/test/conformance/ |
Blueprint ledger vectors, Dingo era validation entry points, and real SQLite/backend behavior; reports counts by era and rule family |
| Deterministic consensus | go test ./ouroboros/ -run TestConsensusConformance |
Five shared scenarios covering origin ingestion, within-k and beyond-k forks, rollback/intersection, tie-breaking, and downstream ChainSync observations |
| Reference node | ./internal/test/devnet/run-tests.sh --conformance |
Explicit Dingo-versus-cardano-node live compatibility profile; it is not run by either deterministic profile |
The release and Linux CI gates run the ledger and deterministic profiles as
part of ./...; the verbose profile reports contain the exact corpus and
scenario counts. Reference-node compatibility remains an explicit DevNet
check and is not represented as passing when that profile was not run.
# Load testdata with CPU and memory profiling
make test-load-profile
# Analyze
go tool pprof cpu.prof
go tool pprof mem.prof
# Enable live pprof on loopback for serve or Mithril sync
DINGO_DEBUG_PORT=6060 ./dingo
go tool pprof http://127.0.0.1:6060/debug/pprof/heapThe live pprof server has no authentication or TLS. Its dedicated
debugBindAddr defaults to 127.0.0.1 even when bindAddr or
privateBindAddr uses a wildcard. External exposure therefore requires an
explicit --debug-bind-addr, DINGO_DEBUG_BIND_ADDR, or debugBindAddr
override and should be protected by a firewall or equivalent network policy.
The default DevNet runs a private all-Dingo Cardano network: three Dingo block
producers, one Dingo relay, and txpump. It validates Dingo-to-Dingo consensus,
block diffusion, liveness, mempool behavior, and Dingo-only features.
Pass --conformance to run Dingo beside cardano-node for compatibility and
reference-conformance testing.
The default Docker Compose profile contains:
| Container | Role | Host Port |
|---|---|---|
dingo-1 |
Dingo block producer (pool 1) | 3010 |
dingo-2 |
Dingo block producer (pool 2) | 3013 |
dingo-3 |
Dingo block producer (pool 3) | 3014 |
dingo-relay |
Dingo relay (no block production) | 3015 |
txpump-dingo |
Submits transactions into Dingo's mempool | — |
The opt-in conformance profile contains dingo-producer,
cardano-producer, cardano-relay, and txpump. A configurator init
container generates fresh pool keys and genesis files for either profile.
- Docker with the Compose plugin (
docker compose) - Go 1.26+
The test suite builds the Dingo Docker image, starts all containers, waits for
health checks, and runs Linux-only Go integration tests tagged with
//go:build linux && devnet. Conformance-only scenarios additionally require
devnet_conformance, while Dingo-only scenarios require
!devnet_conformance:
cd internal/test/devnet/
# Run the all-Dingo suite
./run-tests.sh
# Run Dingo beside cardano-node
./run-tests.sh --conformance
# Run a specific test
./run-tests.sh -run TestBasicBlockForging
# Keep containers running after tests pass (for inspection)
./run-tests.sh --keep-upOverride host ports if needed:
DEVNET_DINGO_PORT=4010 DEVNET_CARDANO_PORT=4011 DEVNET_RELAY_PORT=4012 ./run-tests.shFor longer-running manual tests (soak testing, observing behavior over multiple epochs, debugging):
cd internal/test/devnet/
# Start all containers
./start.sh
# Watch logs
docker compose -f docker-compose.yml logs -f
# Watch a specific node
docker compose -f docker-compose.yml logs -f dingo-1
# Stop and clean up
./stop.shContainers remain running until you stop them. The DevNet parameters (in testnet.yaml) use 1-second slots and 500-slot epochs (~8 minutes per epoch) with activeSlotsCoeff=0.4 and securityParam (k)=40, so you can observe epoch transitions, leader election, and stake snapshot rotation relatively quickly.
See internal/test/devnet/README.md for full details on the harness, configurator, available test scenarios, and port/address overrides.
For quick iteration without Docker, devmode.sh runs Dingo directly against a local devnet genesis. It resets state and updates genesis timestamps on each run:
# Run in devnet mode
./devmode.sh
# With debug logging
DEBUG=true ./devmode.shThis stores state in .devnet/ and uses genesis configs from config/cardano/devnet/. It runs a single Dingo node (no cardano-node counterpart), which is useful for testing startup, block production, and transaction submission in isolation.
The bundled devnet parameters track Yaci DevKit's default local cluster, so a dApp developer moving between the two sees the same chain shape: 1-second slots with activeSlotsCoeff=1.0, so the single producer forges a block every slot, and a 600-slot (10-minute) epoch. securityParam (k)=100 follows Yaci's derivation, which sizes k so the randomness stabilisation window is a fraction of the epoch rather than a multiple of it. Byron k=60 keeps a Byron epoch (10k slots) at the same 600 slots, with a 1-second Byron slot.
These same files ship in the release image as /opt/cardano/config/devnet (from docker-cardano-configs) and are what downstream tooling copies to generate a single-node devnet.