Introduction
Dugite is a Cardano node implementation written in Rust, aiming for 100% compatibility with cardano-node (Haskell).
Built by Sandstone Pool.
Why Dugite?
The Cardano ecosystem benefits from client diversity. Running multiple independent node implementations strengthens the network by:
- Resilience — A bug in one implementation does not bring down the entire network.
- Performance — Rust's zero-cost abstractions and memory safety without garbage collection enable high-throughput block processing.
- Verification — An independent implementation validates the Cardano specification against the reference Haskell node, catching ambiguities and edge cases.
- Accessibility — A Rust codebase broadens the pool of developers who can contribute to Cardano infrastructure.
Key Features
- Full Ouroboros Praos consensus — Slot leader checks, VRF validation, KES period tracking, epoch nonce computation.
- Multi-era support — Byron, Shelley, Allegra, Mary, Alonzo, Babbage, Conway, and Dijkstra eras.
- Conway governance (CIP-1694) — DRep registration, voting, proposals, constitutional committee, treasury withdrawals.
- Pipelined sync — ChainSync headers are pipelined per peer (default depth 300, tunable via
DUGITE_PIPELINE_DEPTH), decoupled from block fetching. Bulk block fetch uses a single active fetch slot contested by the fastest peers, mirroring cardano-node'smaxConcurrencyBulkSync = 1. - Plutus script execution — Plutus V1/V2/V3 (and V4 from Dijkstra) evaluation via the in-house
dugite-uplcCEK machine (fully conformant, all 999 upstream test vectors pass). - Node-to-Node (N2N) protocol — Full Ouroboros mini-protocol suite: ChainSync, BlockFetch, TxSubmission2, KeepAlive, PeerSharing.
- Node-to-Client (N2C) protocol — Unix domain socket server with LocalChainSync, LocalStateQuery, LocalTxSubmission, and LocalTxMonitor.
- UTxO RPC (gRPC) server — Optional native
utxorpcserver (sync, query, submit, watch) with reflection and gRPC-Web support. See UTxO RPC. - cardano-cli compatible CLI — Key generation, transaction building, signing, submission, queries, and governance commands.
- Prometheus metrics — Real-time node metrics on port 12796 by default (deliberately offset from cardano-node's 12798 so both can run on one host).
- P2P networking — Peer manager with cold/warm/hot lifecycle, DNS multi-resolution (A/AAAA/SRV), ledger-based peer discovery, and inbound rate limiting.
- ChainSync Jumping (CSJ) — Phase A Ouroboros Genesis support: dynamic intersection discovery across multiple peers for faster tip-of-chain recovery.
- Mithril snapshot import — Fast initial sync by importing a Mithril-certified snapshot.
- SIGHUP topology reload — Update peer configuration without restarting the node.
Project Status
Dugite is in early development and is NOT recommended for production use. APIs, storage formats, and on-chain behavior may change without notice. Ledger validation is incomplete and may accept invalid transactions or reject valid ones. Do not use this software to operate a stake pool, manage real funds, or participate in mainnet governance. Use at your own risk on testnets only.
Dugite is under active development. It can sync against both the Cardano mainnet and preview/preprod testnets. The node implements the full N2N and N2C protocol stacks, ledger validation, epoch transitions with stake snapshots and reward distribution, and Conway-era governance.
For a detailed checklist of implemented and pending features, see the Developer Wiki.
License
Dugite is released under the Apache-2.0 License.
Installation
Dugite can be installed from pre-built binaries, a container image, Nix, or built from source.
Pre-built Binaries
Download the latest release from GitHub Releases:
| Platform | Architecture | Download |
|---|---|---|
| Linux | x86_64 | dugite-x86_64-linux.tar.gz |
| Linux | aarch64 | dugite-aarch64-linux.tar.gz |
| macOS | Apple Silicon | dugite-aarch64-macos.tar.gz |
Note: macOS x86_64 (Intel) binaries are not published — GitHub's macOS runners are aarch64-only. Intel Mac users should build from source.
Each tarball contains dugite-node, dugite-cli, the config/ tree, README.md, and LICENSE. The two TUIs (dugite-monitor, dugite-config) are not in the release tarballs — get them from the container image or by building from source.
# Example: download and extract for Linux x86_64
curl -LO https://github.com/michaeljfazio/dugite/releases/latest/download/dugite-x86_64-linux.tar.gz
tar xzf dugite-x86_64-linux.tar.gz
sudo mv dugite-node dugite-cli /usr/local/bin/
Verify checksums:
curl -LO https://github.com/michaeljfazio/dugite/releases/latest/download/SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
Container Image
Multi-arch (linux/amd64, linux/arm64) images are published to GitHub Container Registry on every tagged release:
docker pull ghcr.io/michaeljfazio/dugite:latest
# …or pin a release (tag = the release version without the leading "v")
docker pull ghcr.io/michaeljfazio/dugite:<version>
The image is distroless, runs as non-root (uid 65532), ships all four binaries (dugite-node, dugite-cli, dugite-config, dugite-monitor), and bundles the config/ tree at /opt/dugite/config/. ENTRYPOINT is dugite-node; the default command runs a preview relay.
docker run --rm \
-v dugite-db:/opt/dugite/db \
-v dugite-ipc:/opt/dugite/ipc \
-p 3001:3001 \
ghcr.io/michaeljfazio/dugite:latest \
run --config /opt/dugite/config/preview/config.json \
--topology /opt/dugite/config/preview/topology.json \
--database-path /opt/dugite/db \
--socket-path /opt/dugite/ipc/node.sock \
--host-addr 0.0.0.0 --port 3001
A Helm chart is published alongside the image as an OCI artifact:
helm install dugite-relay \
oci://ghcr.io/michaeljfazio/charts/dugite-node \
--set network.name=preview
See Kubernetes Deployment for the full chart reference.
Nix
The repository is a flake (flake.nix plus the modules under nix/), supporting x86_64-linux, aarch64-linux, x86_64-darwin, and aarch64-darwin.
# Build a single binary
nix build github:michaeljfazio/dugite#dugite-node
nix build github:michaeljfazio/dugite#dugite-cli
# Build every binary in one derivation
nix build github:michaeljfazio/dugite#dugite-all
# Development shell (stable Rust via fenix, plus just, jq, fd, ripgrep)
nix develop
packages.default is dugite-node. A NixOS service module lives at nix/nixosModules/service-dugite.nix.
Caveats: the
dugite-tuiflake output is stale — no such crate exists in the workspace (the TUIs aredugite-monitoranddugite-config). The dev shell also does not yet provideprotoc, whichdugite-rpc's build script requires; install it separately (see System Dependencies) until the flake is updated.
Building from Source
Prerequisites
Rust Toolchain
Install the latest stable Rust toolchain via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Verify the installation:
rustc --version
cargo --version
Dugite requires the latest stable Rust toolchain (edition 2021). Use rustup update stable to stay current. The repository does not pin a toolchain — CI and the Nix flake both track stable, so there is no rust-toolchain.toml to honour.
System Dependencies
protoc is required. The dugite-rpc crate generates its gRPC stubs at build time from the vendored UTxO RPC .proto files, and its build script invokes protoc. Without it, cargo build fails on dugite-rpc (and therefore on dugite-node, which depends on it).
# Debian / Ubuntu — libprotobuf-dev supplies the google/protobuf/*.proto well-known types
sudo apt-get install -y protobuf-compiler libprotobuf-dev
# macOS
brew install protobuf
Beyond protoc, there is nothing else to install. The storage layer is pure Rust: block storage uses append-only chunk files, and the UTxO set uses dugite-lsm, a pure Rust LSM tree — no RocksDB, no LMDB, no C toolchain requirement.
Optionally install just to use the top-level task runner (just build, just check, …). See Development.
Build
Clone the repository:
git clone https://github.com/michaeljfazio/dugite.git
cd dugite
Build in release mode:
cargo build --release
# …or, with just installed:
just build
On Linux with kernel 5.1+, you can enable io_uring for improved disk I/O in the UTxO LSM tree. The feature is defined on dugite-storage and re-exported by dugite-node:
cargo build --release --features io-uring
This produces four operator binaries in target/release/:
| Binary | Description |
|---|---|
dugite-node | The Cardano node |
dugite-cli | The cardano-cli compatible command-line interface |
dugite-monitor | Terminal monitoring dashboard (ratatui-based, real-time metrics via Prometheus polling) |
dugite-config | Interactive TUI configuration editor with tree navigation, inline editing, and diff view |
A workspace build also emits internal development helpers (apply_bench, probe_block, replay_phase2, capture-ratification-fixture, xtask). Those are not part of the operator surface and are not shipped in releases.
Install Binaries
To install the binaries into your $CARGO_HOME/bin (typically ~/.cargo/bin/):
cargo install --path crates/dugite-node
cargo install --path crates/dugite-cli --bin dugite-cli
cargo install --path crates/dugite-monitor
cargo install --path crates/dugite-config
(--bin dugite-cli keeps the internal capture-ratification-fixture helper out of your bin/ directory.)
Running Tests
Verify everything is working (requires cargo-nextest):
cargo nextest run --workspace
cargo test --doc
The project enforces a zero-warning policy. Run the full CI gate locally with a single recipe:
just check # fmt-check → clippy → build → test → test-doc
Or invoke the same steps directly:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo build --release --all-targets
cargo nextest run --workspace
cargo test --doc
Development Build
For faster compilation during development, use the debug profile (the default):
cargo build
Debug builds are significantly faster to compile but produce slower binaries. Always use --release for running a node against a live network.
Quick Start
This guide walks you through getting Dugite running on the Cardano preview testnet.
Dugite is in early development and is not recommended for production use. Run it on testnets only — see Project Status.
1. Install
Option A: Pre-built binary (fastest)
The release tarball contains dugite-node, dugite-cli, and the config/ tree.
curl -LO https://github.com/michaeljfazio/dugite/releases/latest/download/dugite-x86_64-linux.tar.gz
tar xzf dugite-x86_64-linux.tar.gz
sudo mv dugite-node dugite-cli /usr/local/bin/
Option B: Container image
docker pull ghcr.io/michaeljfazio/dugite:latest
Multi-arch (linux/amd64, linux/arm64), ships all four binaries, and bundles config/ at /opt/dugite/config/. See Installation.
Option C: Build from source
Requires a stable Rust toolchain and protoc — see Installation.
git clone https://github.com/michaeljfazio/dugite.git
cd dugite
cargo build --release
2. Fast Sync with Mithril (Recommended)
Import a Mithril-certified snapshot to skip syncing the chain from genesis:
dugite-node mithril-import \
--network-magic 2 \
--database-path ./db-preview
This downloads the latest snapshot from the Mithril aggregator, verifies its certificate chain, extracts it, and bulk-imports the blocks into the ImmutableDB. The ancillary archive (the Haskell ledger state at the immutable tip) is downloaded by default, which cuts bootstrap from multi-hour to roughly 15 minutes; pass --no-include-ancillary to replay from blocks instead. See Mithril Snapshot Import for snapshot sizes, disk requirements, and the trust model.
Or via the justfile:
just mithril-import preview
3. Run the Node
Dugite ships with configuration files for mainnet, preview, and preprod, under config/<network>/ — config.json, topology.json, and four genesis files (byron-genesis.json, shelley-genesis.json, alonzo-genesis.json, conway-genesis.json). The release tarball and container image both bundle this tree. Network magic is 764824073 for mainnet, 2 for preview, and 1 for preprod.
dugite-node run \
--config config/preview/config.json \
--topology config/preview/topology.json \
--database-path ./db-preview \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001
Or via the top-level justfile:
just run-relay preview
The node will:
- Load the configuration and genesis files
- Replay imported blocks through the ledger (builds UTxO set, protocol params, delegations)
- Connect to preview testnet peers
- Sync remaining blocks to chain tip
Progress is logged every 5 seconds, showing sync percentage, blocks-per-second throughput, UTxO count, and epoch number. Logs go to stdout by default; add --log-output file --log-dir /var/log/dugite for file logging. See Logging for all options.
4. Query the Node
Once the node is running, query it using the CLI via the Unix domain socket:
# Query the current tip
dugite-cli query tip \
--socket-path ./node.sock \
--testnet-magic 2
Example output (field order matches cardano-cli 10.x — alphabetical, no network field):
{
"block": 4094745,
"epoch": 1232,
"era": "Conway",
"hash": "8498ccda...",
"slot": 106453897,
"slotInEpoch": 9097,
"slotsToEpochEnd": 77303,
"syncProgress": "100.00"
}
# Query protocol parameters
dugite-cli query protocol-parameters \
--socket-path ./node.sock \
--testnet-magic 2
# Query mempool
dugite-cli query tx-mempool info \
--socket-path ./node.sock \
--testnet-magic 2
5. Check Metrics
Prometheus metrics are served on port 12796 by default — deliberately offset from cardano-node's 12798 so both can run on the same host. Override with --metrics-port, or the MetricsPort field in config.json (the shipped configs set 12796 explicitly).
curl -s http://localhost:12796/metrics | grep dugite_sync_progress
# dugite_sync_progress_percent 10000
The value is a percentage scaled by 100 — divide by 100 for percent.
Next Steps
- Configuration — Detailed configuration options
- Networks — Connecting to mainnet, preview, or preprod
- Mithril Import — Fast initial sync details
- Monitoring — Prometheus metrics endpoint
- Kubernetes Deployment — Helm chart for production deployments
- Relay Node — Running relay nodes for a stake pool
- Block Producer — Running a stake pool
- CLI Reference — Full CLI command reference
Development
Day-to-day tasks (build, test, lint, run a network, soak, monitor) are wrapped by a top-level justfile. Install just (brew install just, cargo install just, or your package manager); bare just (or just --list) shows the full menu.
Before your first build, install protoc — dugite-rpc's build script needs it. See System Dependencies.
Recipe arguments are positional, not named: just submit-txs 100, not just submit-txs n=100 (the latter passes the literal string n=100).
Quick reference
# Full CI gate (run this before opening a PR).
just check
# Individual gates
just build # cargo build --release --all-targets
just test # cargo nextest run --workspace
just test-doc # cargo test --doc
just clippy # cargo clippy --all-targets -- -D warnings
just fmt-check # cargo fmt --all -- --check
just fmt # apply rustfmt
# Run a node
just run-relay preview
just run-bp mainnet
just mithril-import preview
# Local devnet (testnet/local-devnet, loopback)
just devnet-setup # one-time: render configs, generate keys, fetch reference binaries
just devnet-run # start dugite-bp + dugite-relay + cardano-node-bp
just devnet-soak # 30-minute soak
just devnet-verify # check evidence from the last run/soak
just devnet-report # single-round report from the latest evidence dir
just devnet-stop
# devnet-validate presets (see the devnet-validate skill)
just devnet-validate-smoke # single boot, ~5 min — PR gate for core crates
just devnet-validate-extended # 3 rounds, ~75 min — used for release tagging
# Preview Sandstone soak
just soak-6h # Haskell relay + dugite BP, 6h orchestrator
just soak-bare-bp # dugite BP alone
just soak-status
# Monitoring (Prometheus + Grafana via Docker)
just monitor-start # optional arg: Prometheus port (default 9090)
just monitor-status
just monitor-stop
just watch-metrics # tail the Prometheus endpoint (default port 12796)
# Validation
just compat-n2c # cardano-cli vs dugite-cli N2C diff
just compat-leader # leader-schedule N2C diff
just submit-txs 100 # submit N test transactions (positional arg)
just stress-test
just stress-relay
just benchmark-pipeline
# Dual-decode validation (in-house decoder vs shadow decoder)
just dual-decode-smoke # serialization tests with DUGITE_DUAL_DECODE=panic
just dual-decode-soak preview 0 # NETWORK, MAX_BLOCKS (0 = unlimited), extra flags
just dual-decode-report # summarise mismatch artefacts
# Upstream conformance (corpus pinned in tests/conformance/upstream/manifest.toml)
just download-upstream-fixtures # all seven fixture areas at the pinned tag
just download-upstream-fixtures-area plutus
just test-conformance # UPLC + every upstream golden test
just test-conformance-uplc # 999 plutus-core evaluation vectors
just test-conformance-upstream # all upstream goldens in one binary
just regenerate-corpus-local # rebuild corpus tarballs locally
# Dev / release helpers
just licenses # regenerate docs/src/reference/third-party-licenses.md
just clean-worktrees # prune stale git worktree branches
just query-tip # dugite-cli query tip against ./node.sock
just bump-utxorpc-spec v0.19.2 # refresh vendored utxorpc/spec protos
Per-area conformance filters also exist (just test-conformance-cardano-base, -cardano-ledger, -cardano-node, -ledger-rules, -mithril, -ouroboros-consensus, -status). These are for iteration only — the "N skipped" count they print is the tests belonging to other areas, not a coverage gap. Use just test-conformance-upstream for the unfiltered run.
Layout
Most recipes wrap scripts under scripts/<group>/; the devnet recipes wrap testnet/local-devnet/:
| Group | Path |
|---|---|
| Run | scripts/run/{bp,relay}-{mainnet,preview,preprod}.sh, scripts/run/dual-node.sh, scripts/run/haskell-relay-preview.sh |
| Soak | scripts/soak/run-6h.sh (entry point; backgrounds orchestrator-6h.sh), run-bare-bp.sh, status-6h.sh + helpers |
| Local devnet | testnet/local-devnet/{setup,run,soak,verify,stop,submit-txs,run-genesis}.sh |
| Monitoring | scripts/monitoring/start.sh, watch-metrics.sh, health-check.sh, bp-watch.sh, relay-watchdog.sh |
| Validation | scripts/validation/n2c-compat-test.sh, leader-schedule-compat.sh, stress-test.sh, stress-test-5k.sh, relay-stress-test.sh, submit-txs.sh, benchmark-pipeline-depth.sh, dual-decode-soak.sh, dual-decode-report.py |
| Mithril | scripts/mithril/import.sh |
| Conformance | scripts/regenerate-conformance-corpus/regenerate.sh + per-area capture-*.sh |
| Dev | scripts/dev/check.sh, generate-licenses.py, cleanup-worktree-branches.sh, query-tip.sh, bump-utxorpc-spec.sh |
Most scripts cd "$(dirname "$0")/../.." before doing work, so they resolve ./config/ and ./target/release/ regardless of cwd — you can invoke them via just, from the repo root, or by absolute path. A minority (notably some under scripts/monitoring/ and scripts/validation/) assume the repo root; prefer the just recipe. See scripts/README.md and config/README.md for the canonical layout description.
Hard requirements
- Zero warnings —
cargo clippy --all-targets -- -D warnings(also run byjust clippyandjust check) - Formatted —
cargo fmt --all -- --check(just fmt-check) - All tests pass —
cargo nextest run --workspaceandcargo test --doc(just test,just test-doc) - CI green before merging
- Focused commits — stage explicit filenames; the pre-commit hook warns if staged paths span more than two crates (set
DUGITE_PRECOMMIT_STRICT=1to make this fatal)
Configuration
Dugite reads a JSON configuration file that controls network settings, genesis file paths, P2P parameters, and tracing options. The format is compatible with the cardano-node configuration format.
Configuration File Format
The configuration file uses PascalCase keys (matching the cardano-node convention). This is config/preview/config.json as shipped in the repository:
{
"Network": "Testnet",
"NetworkMagic": 2,
"DiffusionMode": "InitiatorAndResponder",
"ByronGenesisFile": "byron-genesis.json",
"ByronGenesisHash": "81cf23542e33d64c541699926c2b5e6e9c286583f0c8a3fb5f22ea7b352dd174",
"ShelleyGenesisFile": "shelley-genesis.json",
"ShelleyGenesisHash": "363498d1024f84bb39d3fa9593ce391483cb40d479b87233f868d6e57c3a400d",
"AlonzoGenesisFile": "alonzo-genesis.json",
"ConwayGenesisFile": "conway-genesis.json",
"TargetNumberOfRootPeers": 60,
"TargetNumberOfActivePeers": 15,
"TargetNumberOfEstablishedPeers": 30,
"TargetNumberOfKnownPeers": 85,
"TargetNumberOfActiveBigLedgerPeers": 5,
"TargetNumberOfEstablishedBigLedgerPeers": 10,
"TargetNumberOfKnownBigLedgerPeers": 15,
"MinSeverity": "Info",
"LogDirective": "info",
"MetricsPort": 12796,
"ExperimentalHardForksEnabled": true
}
Unknown keys are ignored. The node's deserializer does not reject fields it does not recognise, so a typo in a key name silently leaves the default in force with no warning. Run
dugite-config validate <file>to have unknown keys reported — see Configuration Editor.
Fields Reference
Network Settings
| Field | Type | Default | Description |
|---|---|---|---|
Network | string | "Mainnet" | Network identifier: "Mainnet" or "Testnet" |
NetworkMagic | integer | auto | Network magic number. If omitted, derived from Network (764824073 for mainnet) |
DiffusionMode | string | "InitiatorAndResponder" | Controls inbound connection acceptance. "InitiatorAndResponder" (default): relay mode, accepts inbound N2N connections. "InitiatorOnly": block producer mode, outbound only (no listening port opened) |
PeerSharing | boolean/null | null | Enable the peer sharing mini-protocol. When null (default), peer sharing is automatically disabled for block producers (when --shelley-kes-key is provided) and enabled for relays. Set explicitly to override |
ConsensusMode | string | "Praos" | "Praos" (default) or "Genesis" (trustless bulk sync). "PraosMode" / "GenesisMode" are accepted legacy aliases. The --consensus-mode CLI flag, taking praos or genesis, overrides this field |
ExperimentalHardForksEnabled | boolean | false | Advertise readiness for the next major protocol version. false → the node signals ProtVer 11 0 in forged headers and rejects headers whose on-chain protocol version exceeds 11; true → signals ProtVer 12 0 (Dijkstra) and accepts up to 12. Must stay false on mainnet. The shipped config/preview/config.json sets it true |
EnableP2Pis not a Dugite config key. Dugite is always P2P; there is no non-P2P path to switch off. If your config file carriesEnableP2P(from a cardano-node 8.x config) the node ignores it silently.
Protocol
| Field | Type | Default | Description |
|---|---|---|---|
Protocol | string/object | absent | Accepts either a bare string (e.g. "Cardano", which is ignored) or an object carrying RequiresNetworkMagic |
Protocol.RequiresNetworkMagic | string | "RequiresMagic" | Whether network magic is required in the handshake |
RequiresNetworkMagic | string | none | The same setting at the top level, for guild-style and newer cardano-node configs |
These three are inert. They are parsed so a cardano-node config file drops in without error, but nothing in the workspace reads them — Dugite always sends the network magic in the N2N handshake.
NetworkMagicis the field that actually decides which network you join.
Genesis Files
Genesis file paths are resolved relative to the directory containing the configuration file. For example, if your config is at /opt/cardano/config.json and specifies "ShelleyGenesisFile": "shelley-genesis.json", Dugite will look for /opt/cardano/shelley-genesis.json.
| Field | Type | Default | Description |
|---|---|---|---|
ByronGenesisFile | string | none | Path to Byron genesis JSON |
ShelleyGenesisFile | string | none | Path to Shelley genesis JSON |
AlonzoGenesisFile | string | none | Path to Alonzo genesis JSON |
ConwayGenesisFile | string | none | Path to Conway genesis JSON |
DijkstraGenesisFile | string | none | Path to Dijkstra genesis JSON (post-Conway HFC). Parsed at startup but not yet applied to runtime ledger rules. Overridden by the --dijkstra-genesis CLI flag |
ByronGenesisHash | string | none | Expected Blake2b-256 of the Byron genesis file, as 64 hex characters |
ShelleyGenesisHash | string | none | Expected Blake2b-256 of the Shelley genesis file |
AlonzoGenesisHash | string | none | Expected Blake2b-256 of the Alonzo genesis file |
ConwayGenesisHash | string | none | Expected Blake2b-256 of the Conway genesis file |
DijkstraGenesisHash | string | none | Expected Blake2b-256 of the Dijkstra genesis file |
At startup the node checks that every configured genesis file exists (resolved against the config file's directory) and that every configured hash is exactly 64 hex characters. Either check failing is a fatal startup error naming the era.
Tip: Genesis files for each network can be downloaded from the Cardano Operations Book.
P2P Parameters
These parameters control the P2P peer governor's target counts, matching the cardano-node defaults. The governor continuously works to maintain these targets by promoting/demoting peers and discovering new ones.
| Field | Type | Default | Description |
|---|---|---|---|
TargetNumberOfRootPeers | integer | 60 | Target number of root peers (bootstrap + local + public roots) |
TargetNumberOfActivePeers | integer | 20 | Target number of active (hot) peers — fully syncing with ChainSync + BlockFetch |
TargetNumberOfEstablishedPeers | integer | 30 | Target number of established (warm) peers — TCP connected, keepalive running |
TargetNumberOfKnownPeers | integer | 150 | Target number of known (cold) peers in the peer table |
TargetNumberOfActiveBigLedgerPeers | integer | 5 | Target number of active big ledger peers (high-stake SPOs, prioritised during sync) |
TargetNumberOfEstablishedBigLedgerPeers | integer | 10 | Target number of established big ledger peers |
TargetNumberOfKnownBigLedgerPeers | integer | 15 | Target number of known big ledger peers |
Two of these are advisory in Dugite rather than direct policy levers:
TargetNumberOfRootPeers is validated and exported as a Prometheus gauge, but
root-peer connectivity is actually driven per-group by the topology's
hotValency / warmValency; and TargetNumberOfKnownBigLedgerPeers is not
enforced as a separate cap, because the known set as a whole is bounded by
TargetNumberOfKnownPeers and selectively forgetting scarce big ledger peers
would hurt Genesis sync.
Genesis-mode sync targets
A second target set applies while the node is in Genesis-mode bulk sync. It is
parsed and validated unconditionally, but only takes effect when
ConsensusMode is "Genesis".
| Field | Type | Default | Description |
|---|---|---|---|
SyncTargetNumberOfActivePeers | integer | 5 | Active peers during Genesis bulk sync |
SyncTargetNumberOfEstablishedPeers | integer | 10 | Established peers during Genesis bulk sync |
SyncTargetNumberOfKnownPeers | integer | 150 | Known peers during Genesis bulk sync |
SyncTargetNumberOfRootPeers | integer | 0 | Root peers during Genesis bulk sync |
SyncTargetNumberOfActiveBigLedgerPeers | integer | 30 | Active big ledger peers during Genesis bulk sync |
SyncTargetNumberOfEstablishedBigLedgerPeers | integer | 40 | Established big ledger peers during Genesis bulk sync |
SyncTargetNumberOfKnownBigLedgerPeers | integer | 100 | Known big ledger peers during Genesis bulk sync |
MinBigLedgerPeersForTrustedState | integer | 5 | Pause sync if active big ledger peers drop below this |
Startup validation
Both target sets are checked at startup against the same predicates as Haskell's
sanePeerSelectionTargets, and a violation is a fatal startup error naming the
set ([deadline] or [sync]):
active <= established <= known, androot <= knownactiveBigLedger <= establishedBigLedger <= knownBigLedgeractive <= 100,established <= 1000,known <= 10000(and the same three ceilings for the big-ledger-peer counts)
Churn
| Field | Type | Default | Description |
|---|---|---|---|
ChurnIntervalNormalSecs | integer | 3300 | Governor churn interval while caught up (55 min, matching cardano-node) |
ChurnIntervalSyncSecs | integer | 900 | Governor churn interval while syncing (15 min) |
Ouroboros Genesis Tuning
LowLevelGenesisOptions mirrors cardano-node's object of the same name and is
only consulted when ConsensusMode is "Genesis". Omit the whole object to get
the upstream defaults.
"LowLevelGenesisOptions": {
"EnableCSJ": true,
"EnableLoEAndGDD": true,
"EnableLoP": true,
"BlockFetchGracePeriod": 10,
"BucketCapacity": 100000,
"BucketRate": 500,
"CSJJumpSize": 4320,
"GDDRateLimit": 1.0
}
| Field | Type | Default | Description |
|---|---|---|---|
EnableCSJ | boolean | true | Enable ChainSync Jumping |
EnableLoEAndGDD | boolean | true | Enable Limit on Eagerness + Genesis Density Disconnection |
EnableLoP | boolean | true | Enable the Limit on Patience leaky bucket |
BlockFetchGracePeriod | float | 10 | Seconds before rotating a starving bulk-sync peer |
BucketCapacity | integer | 100000 | LoP bucket capacity, in tokens |
BucketRate | integer | 500 | LoP bucket leak rate, in tokens/second |
CSJJumpSize | integer | 4320 | CSJ jump size in slots (2 × 2160, the Byron forecast range) |
GDDRateLimit | float | 1.0 | Minimum seconds between GDD evaluations |
SnapshotMinIntervalBulkSync | float | 1800 | Dugite-specific (not a cardano-node key). Minimum wall-clock seconds between epoch-boundary ledger snapshots during bulk sync. Raising it cuts snapshot I/O; lowering it shrinks the rollback blast radius on an unexpected stop |
Checkpoints
| Field | Type | Default | Description |
|---|---|---|---|
CheckpointsFile | string | none | Path to a lightweight-checkpoints JSON file ({"checkpoints":[{"blockNo":N,"hash":"<hex>"},...]}), resolved relative to the config file's directory. Checkpoints are enforced for every header in both consensus modes |
CheckpointsFileHash | string | none | Blake2b-256 hex of the checkpoints file bytes. A mismatch is a fatal startup error |
Connection Management
| Field | Type | Default | Description |
|---|---|---|---|
AcceptedConnectionsLimit.hardLimit | integer | 512 | Refuse new inbound connections beyond this count |
AcceptedConnectionsLimit.softLimit | integer | 384 | Start delaying new inbound connections at this count |
AcceptedConnectionsLimit.delay | float | 5.0 | Maximum delay in seconds applied above the soft limit |
PerIpRateLimitN2n | integer | 5 | Maximum concurrent N2N inbound connections per source IP. 0 disables per-IP limiting (not recommended) |
MaxN2cConnections | integer | 16 | Maximum concurrent N2C (Unix socket) connections |
BlockFetchMaxRange | integer | max | Maximum blocks pulled by a single BlockFetch MsgRequestRange. Clamped to [64, 2000] at use; omitted means the 2000-block network cap. The DUGITE_BLOCKFETCH_MAX_RANGE env var overrides this field |
AcceptedConnectionsLimit also accepts the older long key names
(acceptedConnectionsHardLimit, acceptedConnectionsSoftLimit,
acceptedConnectionsDelay) as aliases.
The following four keys are parsed for cardano-node config-file compatibility but not currently enforced. Setting them changes nothing; they are reserved so that a cardano-node config drops in without a parse error.
| Field | Type | Default | Why it is inert |
|---|---|---|---|
ProtocolIdleTimeout | float | 5.0 | Dugite prunes idle connections via the connection manager's own 300 s INBOUND_IDLE_TIMEOUT |
TimeWaitTimeout | float | 60.0 | Dugite relies on the OS TCP TIME_WAIT |
EgressPollInterval | float | 0.0 | The governor runs on a fixed, tuned 2 s tick |
ChainSyncIdleTimeout | float | none | Dugite randomises the timeout between Haskell's minChainSyncTimeout / maxChainSyncTimeout bounds; a fixed override would defeat that |
Metrics, RPC, and Storage
| Field | Type | Default | Description |
|---|---|---|---|
MetricsPort | integer | 12796 | Prometheus metrics port. 0 disables the server. Dugite's default is deliberately offset from cardano-node's 12798 so both can run on one host |
TurnOnLogMetrics | boolean | true | Master switch for the metrics endpoint, matching cardano-node. false disables the server regardless of MetricsPort |
Rpc | object | none | UTxO RPC (gRPC) server block: Enabled, ListenAddr, Port, MaxConcurrentStreams, StreamBufferSize, ReflectionEnabled, WebEnabled, AlphaEnabled, Tls: {CertPath, KeyPath}. See UTxO RPC |
Storage | object | none | Storage overrides layered on top of --storage-profile (index type, UTxO backend, LSM memtable/cache/bloom sizing) |
Effective metrics port, highest precedence first: --no-metrics → 0;
--metrics-port <PORT>; TurnOnLogMetrics: false → 0; MetricsPort;
otherwise 12796. Note that an explicit --metrics-port wins even over
TurnOnLogMetrics: false.
Tracing
| Field | Type | Default | Description |
|---|---|---|---|
MinSeverity | string | "Info" | Minimum log severity, in cardano-node's syslog vocabulary: Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency. Mapped onto tracing levels — Notice → info, and Critical/Alert/Emergency → error. Anything unrecognised falls back to info |
LogDirective | string | none | RUST_LOG-style filter directive (e.g. "info,dugite_network=trace"). Applied at startup and on SIGHUP, and takes precedence over MinSeverity |
TraceOptions.TraceBlockFetchClient | boolean | false | Trace block fetch client activity |
TraceOptions.TraceBlockFetchServer | boolean | false | Trace block fetch server activity |
TraceOptions.TraceChainDb | boolean | false | Trace ChainDB operations |
TraceOptions.TraceChainSyncClient | boolean | false | Trace chain sync client activity |
TraceOptions.TraceChainSyncServer | boolean | false | Trace chain sync server activity |
TraceOptions.TraceForge | boolean | false | Trace block forging |
TraceOptions.TraceMempool | boolean | false | Trace mempool activity |
Log Level Control
Verbosity is resolved with this precedence, highest first:
RUST_LOGenvironment variable--log-levelCLI flagLogDirectiveconfig fieldMinSeverityconfig field
# Via CLI flag
dugite-node run --log-level debug ...
# Via environment variable (takes priority over --log-level)
RUST_LOG=info dugite-node run ...
# Debug only for specific crates
RUST_LOG=dugite_network=debug,dugite_consensus=debug dugite-node run ...
Because the config file is read after the tracing subscriber is constructed, the
config-file values are applied via a live filter reload immediately after
startup — but only when neither RUST_LOG nor --log-level is set, so an
explicit operator override is never clobbered by the file.
That guard applies at startup only. A later SIGHUP applies LogDirective /
MinSeverity unconditionally, so a reload will override a level you set on
the command line or in the environment.
Dugite supports multiple log output targets (stdout, file, journald) and file rotation. See Logging for full details on output configuration.
Live Reload (SIGHUP)
Sending SIGHUP re-reads both the config file and the topology file. Changed
fields are partitioned into those that can be applied live and those that need a
restart; restart-required changes are logged as warnings and the reloadable ones
are still applied.
Hot-reloadable: all seven TargetNumberOf* deadline targets,
ChurnIntervalNormalSecs, ChurnIntervalSyncSecs, LogDirective,
MinSeverity.
Restart required: Network, NetworkMagic, every genesis file path and
hash, MetricsPort, TurnOnLogMetrics, DiffusionMode,
ExperimentalHardForksEnabled, ConsensusMode — plus everything set on the
command line (database and socket paths, listen address and port, KES/VRF/OpCert
paths).
kill -HUP $(pgrep -x dugite-node)
Stopping the Node
Always stop the node with SIGTERM (or SIGINT / Ctrl-C), never SIGKILL.
kill -TERM $(pgrep -x dugite-node) # correct
kill -9 $(pgrep -x dugite-node) # do not do this
On SIGTERM the node demotes its peers, flushes storage, and writes a final
ledger snapshot. A hard kill skips all of that and risks damaging the active
ImmutableDB chunk's secondary index. Since v2.4.0 the node reconciles that
damage at open — verifying the tail chunk by CRC, truncating to the verified
prefix, and quarantining an index-less tail chunk as .chunk.orphaned — but
recovery still costs the un-flushed blocks, and damage below the tail chunk is a
hard InconsistentChunk error rather than something the node repairs.
A second SIGINT/SIGTERM during shutdown forces an immediate exit, matching
cardano-node's behaviour, so you never need kill -9 to get out of a wedged
shutdown.
ChainDB::open takes an exclusive advisory flock on <database-path>/lock,
so a second process pointed at the same directory fails fast and names the pid
already holding it, instead of two nodes silently interleaving writes.
Minimal Configuration
The smallest viable configuration file specifies only the network:
{
"Network": "Testnet",
"NetworkMagic": 2
}
All other fields use the defaults tabulated above. Note that with no genesis files specified the node falls back to built-in default protocol parameters, which will not match a real network — for anything beyond a smoke test, point at the genesis files for the network you are joining.
Format Support
The parser picks its format from the file extension: .json is parsed as
cardano-node-compatible JSON, and anything else is parsed as TOML. If the
path does not exist at all, the node starts on built-in defaults rather than
failing — so a typo in --config produces a mainnet-default node, not an error.
Editing Configuration Interactively
dugite-config is a standalone TUI for browsing and editing these files with
per-field type validation, tuning hints, a diff view, and a save-and-SIGHUP live
reload. See Configuration Editor (dugite-config).
Configuration Editor (dugite-config)
dugite-config is a standalone TUI tool for creating and editing Dugite configuration files interactively. It provides a full-screen terminal interface with tree navigation, inline editing, type validation, and a diff view — no need to remember field names or look up valid ranges.

Installation
dugite-config is built as part of the standard workspace:
cargo build --release -p dugite-config
cp target/release/dugite-config /usr/local/bin/
Commands
| Command | Description |
|---|---|
edit | Launch the full-screen TUI editor, optionally attaching to a running node |
init | Write a default configuration file for a named network |
validate | Validate a configuration file against the parameter schema |
get | Print the value of a single parameter |
set | Set the value of a single parameter non-interactively |
Argument shape:
getandsettake the key (and value) as positional arguments and the file as a--configflag — not the other way round.
edit
# Edit an explicit file
dugite-config edit config/preview/config.json
# Attach to a running node: discovers dugite-node processes on this machine,
# auto-attaches if exactly one is running, otherwise shows a selector
dugite-config edit
| Flag | Description |
|---|---|
<config_file> | Optional positional path. Omit to auto-discover running dugite-node instances |
--node-pid-file <PATH> | File containing the running node's PID, used by Ctrl+R to send SIGHUP. Ignored in discovery mode, where the discovered process's OS PID is used directly. Defaults to ./logs/bp-pair/bp.pid |
In discovery mode the command exits with an error if no dugite-node process is
running, or if the discovered processes have no readable config file.
init
init is not an interactive wizard. It writes a complete default config for
a named network in one shot:
# Write a preview default config
dugite-config init --network preview --out config.json
# Print to stdout instead
dugite-config init --network mainnet
| Flag | Description |
|---|---|
--network, -n | Required. One of mainnet, preview, preprod |
--out, -o | Output path. Prints to stdout when omitted |
validate
Check a configuration file against the schema without modifying it. Exits 0 when valid and 1 when it contains errors, so it drops straight into CI:
dugite-config validate config/preview/config.json
Known keys are validated against their declared type and range. Keys that are
not in the schema are reported as warnings, not errors — this is how a stray
EnableP2P or a typo'd key name gets surfaced, since the node itself ignores
unknown keys silently.
Output on success (written to stderr):
OK — 'config/preview/config.json' is valid (21 parameters, 0 unknown).
Output on failure:
Errors:
'TargetNumberOfActivePeers': value 200 exceeds maximum (100)
Error: 'config/preview/config.json' failed validation: 1 error(s)
get / set
Non-interactive field access for scripting:
# Get a field
dugite-config get TargetNumberOfActivePeers --config config.json
# Output: 15
# Get with the schema's type, default, section, description, and tuning hint
dugite-config get TargetNumberOfActivePeers --config config.json --verbose
# Set a field (creates config.json.bak first)
dugite-config set TargetNumberOfActivePeers 30 --config config.json
set validates the new value against the schema before writing, and coerces it
to the JSON type the schema declares. A key that is in the schema but absent
from the file is appended; a key in neither is rejected.
Only top-level keys are addressable. There is no dotted-path syntax, so
nested values such as TraceOptions.TraceForge or Rpc.Port must be edited in
the TUI (or by hand).
Interactive Editor
The interactive editor (dugite-config edit) renders a full-screen TUI with two
panels: the parameter tree on the left (60%) and a description panel on the
right (40%). Below 80 columns the right panel is hidden and the tree fills the
terminal.
┌─ Parameters ──────────────────────────────┬─ TargetNumberOfActivePeers ────────┐
│ Network │ Type: integer (1-100) │
│ Network Testnet │ Default: 20 │
│ NetworkMagic 2 │ Section: Network │
│ DiffusionMode InitiatorAndResponder│ │
│ TargetNumberOfActivePeers 15 │ Target number of fully active (hot)│
│ TargetNumberOfEstablishedPeers 30 │ peers. Raising this improves │
│ Genesis │ propagation at the cost of CPU and │
│ ByronGenesisFile byron-genesis.json │ bandwidth. │
│ ShelleyGenesisFile shelley-genesis.json │ │
│ Logging │ Hint: 20 is the cardano-node │
│ MinSeverity Info │ default. BPs may want 10-15. │
└───────────────────────────────────────────┴────────────────────────────────────┘
Parameters are grouped into the sections Network, Genesis, Protocol, Logging, Diffusion, Storage, Rpc, and Advanced. Each schema entry also records whether the parameter is hot-reloadable or needs a restart.
Key bindings
| Key | Action |
|---|---|
j / Down | Move cursor down |
k / Up | Move cursor up |
Enter / e | Edit selected parameter — toggles a boolean, cycles an enum, or opens a text buffer for string/number/path |
Tab | Collapse / expand the current section |
/ | Enter search mode (fuzzy filter) |
Esc | Cancel the current edit, close search, or close the diff overlay |
Ctrl+D | Toggle the diff overlay (original vs. current) |
Ctrl+S | Save to disk — does not exit |
Ctrl+R | Save and send SIGHUP to the running node (live reload) |
q | Quit, prompting if there are unsaved changes |
Note that Ctrl+S saves and stays in the editor; use q to leave. There is no
Ctrl+Q binding and no ? help overlay.
Inline editing
Pressing Enter on a field acts according to the field's type: booleans toggle
immediately, enums cycle to the next choice, and string/number/path fields open
a text buffer. In the text buffer, Enter confirms and Escape cancels.
Validation runs on confirmation against the schema's declared type and range.
Saving
Every save — from Ctrl+S, Ctrl+R, or the set subcommand — first copies the
original file to <path>.bak. Only one level of backup is kept; the previous
.bak is overwritten. Files are written with 4-space indentation and a trailing
newline, matching the official Cardano config files.
Parameters that are absent from the file are shown in the tree seeded from their schema default, and are only written out if you actually change them — so saving does not balloon a minimal config into a fully-pinned one.
Live reload (Ctrl+R)
Ctrl+R saves and then sends SIGHUP to the running node, applying the
hot-reloadable subset of the config without a restart. If the PID cannot be
resolved the config is still saved and the signal is skipped with an error
message. See Live Reload for which
fields actually take effect live.
Search and filter
Press / to enter search mode, which fuzzy-filters the tree as you type.
Backspace deletes, Enter confirms and returns to browse mode with the cursor
on the first match, and Escape clears the filter. Note that j and k move
the cursor while in search mode rather than being typed into the query.
Diff overlay
Ctrl+D opens an overlay comparing the on-disk original against your pending
changes. While the overlay is showing, only Esc is accepted — it closes the
overlay.
Scripted workflows
dugite-config can be used in deployment scripts for automated configuration
management:
#!/usr/bin/env bash
# Example: configure a relay node for preview testnet
set -euo pipefail
CONFIG="config/preview/config.json"
dugite-config init --network preview --out "$CONFIG"
dugite-config set DiffusionMode InitiatorAndResponder --config "$CONFIG"
dugite-config set TargetNumberOfActivePeers 15 --config "$CONFIG"
dugite-config set TargetNumberOfEstablishedPeers 30 --config "$CONFIG"
dugite-config set TargetNumberOfKnownPeers 85 --config "$CONFIG"
dugite-config validate "$CONFIG"
Topology
The topology file defines the peers that the node connects to. Dugite supports the full cardano-node 10.x+ P2P topology format.
Topology File Format
{
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 },
{ "address": "backbone.mainnet.emurgornd.com", "port": 3001 }
],
"localRoots": [
{
"accessPoints": [
{ "address": "192.168.1.100", "port": 3001 }
],
"advertise": false,
"hotValency": 1,
"warmValency": 2,
"trustable": true
}
],
"publicRoots": [
{
"accessPoints": [
{ "address": "relays-new.cardano-mainnet.iohk.io", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 0,
"peerSnapshotFile": "peer-snapshot.json"
}
Peer Categories
Bootstrap Peers
Trusted peers from founding organizations, used during initial sync. These are the first peers the node contacts when starting.
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 }
]
Bootstrap peers are unconditionally trustable — there is no per-entry flag.
They are what satisfies the Honest-Availability-Assumption closure while the
node is syncing, so a topology with none configured has to fall back on
trustable local roots instead.
Set to null or an empty array to disable bootstrap peers:
"bootstrapPeers": null
Local Roots
Peers the node should always maintain connections with. Typically used for:
- Your block producer (if running a relay)
- Peer arrangements with other stake pool operators
- Trusted relay nodes you operate
"localRoots": [
{
"accessPoints": [
{ "address": "192.168.1.100", "port": 3001 }
],
"advertise": true,
"hotValency": 2,
"warmValency": 3,
"trustable": true,
"behindFirewall": false,
"diffusionMode": "InitiatorAndResponder"
}
]
| Field | Type | Default | Description |
|---|---|---|---|
accessPoints | array | required | List of {address, port} entries |
advertise | boolean | false | Whether to share these peers via peer sharing protocol |
valency | integer | 1 | Deprecated. Target number of active connections. Use hotValency instead |
hotValency | integer | valency | Target number of hot (actively syncing) peers. Takes precedence over valency when both are set |
warmValency | integer | hotValency + 1 | Target number of warm (connected, not syncing) peers |
trustable | boolean | false | Whether these peers are trusted for sync. Trusted peers are preferred during initial sync, and the node disconnects from non-trusted peers when syncing from outdated state. Also accepted as trust_able |
behindFirewall | boolean | false | If true, the node waits for inbound connections from these peers instead of connecting outbound |
diffusionMode | string | "InitiatorAndResponder" | Per-group diffusion mode. "InitiatorOnly" for unidirectional connections |
Dugite's peer governor drives root-peer connectivity from these per-group
valencies, not from the aggregate TargetNumberOfRootPeers config field — so
hotValency / warmValency are the levers that actually change behaviour here.
Public Roots
Publicly known nodes (e.g., IOG relays) serving as fallback peers before the node has synced to the useLedgerAfterSlot threshold.
"publicRoots": [
{
"accessPoints": [
{ "address": "relays-new.cardano-mainnet.iohk.io", "port": 3001 }
],
"advertise": false
}
]
Ledger-Based Peer Discovery
After the node syncs past the useLedgerAfterSlot slot, it discovers peers from stake pool registrations in the ledger state. This provides decentralized peer discovery without relying on centralized relay lists.
"useLedgerAfterSlot": 177724800
Set to a negative value or omit to disable ledger peer discovery. 0 enables it
immediately — which is what the shipped config/mainnet/topology.json uses.
Peer Snapshot File
Optional path to a big ledger peer snapshot, used to seed the big-ledger-peer
candidate pool at startup before the live ledger has caught up far enough for
useLedgerAfterSlot discovery to populate it:
"peerSnapshotFile": "peer-snapshot.json"
The path is resolved relative to the topology file's directory (matching
cardano-node), not the config file's. Two shapes are accepted: the IOG
cardano-node 10.x format with a bigLedgerPools array of {relays: [{address, port}]}, and a legacy flat array of {addr, port} objects. Entries from either
shape are treated as big ledger peers. Hostnames are resolved once, at startup.
Legacy Producers Format
The pre-P2P producers list is still parsed, for older topology files:
"producers": [
{ "addr": "relay.example.com", "port": 3001, "valency": 1 }
]
Legacy producers are registered as untrusted, non-advertised peers. Prefer
localRoots / publicRoots for anything new.
Example Topologies
These are the topology files shipped in the repository under
config/<network>/topology.json.
Preview Testnet Relay
{
"bootstrapPeers": [
{ "address": "preview-node.play.dev.cardano.org", "port": 3001 }
],
"localRoots": [
{ "accessPoints": [], "advertise": false, "trustable": false, "valency": 1 }
],
"publicRoots": [
{
"accessPoints": [
{ "address": "preview-node.play.dev.cardano.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 102729600
}
Preprod Testnet Relay
{
"bootstrapPeers": [
{ "address": "preprod-node.play.dev.cardano.org", "port": 3001 }
],
"localRoots": [
{ "accessPoints": [], "advertise": false, "trustable": false, "valency": 1 }
],
"publicRoots": [
{
"accessPoints": [
{ "address": "preprod-node.play.dev.cardano.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 76723200
}
Mainnet Relay
{
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 },
{ "address": "backbone.mainnet.emurgornd.com", "port": 3001 }
],
"localRoots": [],
"publicRoots": [
{
"accessPoints": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 0,
"peerSnapshotFile": "peer-snapshot.json"
}
Relay with Block Producer
A relay node that maintains a connection to your block producer:
{
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 }
],
"localRoots": [
{
"accessPoints": [
{ "address": "10.0.0.10", "port": 3001 }
],
"advertise": false,
"hotValency": 1,
"warmValency": 2,
"trustable": true,
"behindFirewall": true
}
],
"publicRoots": [
{ "accessPoints": [], "advertise": false }
],
"useLedgerAfterSlot": 177724800
}
DNS SRV Resolution
When a hostname is specified in any accessPoints entry, Dugite queries DNS for SRV records at _cardano._tcp.<host> before falling back to A/AAAA lookup — matching the behaviour of the Haskell cardano-node. SRV records carry port, priority, and weight fields (RFC 2782); Dugite honours priority ordering and performs a weighted shuffle within equal-priority groups.
If no SRV records exist (NXDOMAIN or empty answer), Dugite falls back to a direct A/AAAA lookup using the port specified in the topology entry.
IPv4 and IPv6 addresses are both accepted; Dugite resolves A and AAAA records concurrently.
SIGHUP Topology Reload
Dugite supports live topology reloading. Send a SIGHUP signal to the running node process, and it will re-read the topology file and update the peer manager with the new configuration:
kill -HUP $(pgrep -x dugite-node)
This allows you to add or remove peers without restarting the node.
The same signal also re-reads the node config file. Peer targets, churn intervals, and log verbosity are applied live; everything else is logged as needing a restart. See Live Reload.
If you use dugite-config edit, Ctrl+R saves and sends this signal for you.
Networks
Dugite can connect to any Cardano network. Each network is identified by a unique magic number used during the N2N handshake.
Network Magic Values
| Network | Magic | Description |
|---|---|---|
| Mainnet | 764824073 | The production Cardano network |
| Preview | 2 | Fast-moving testnet for early feature testing |
| Preprod | 1 | Stable testnet that mirrors mainnet behavior |
Ready-Made Configs
The repository ships complete, self-contained config and topology files for all
three networks under config/<network>/, alongside the genesis files they
reference:
config/mainnet/{config,topology,byron-genesis,shelley-genesis,alonzo-genesis,conway-genesis}.json
config/preview/{...}
config/preprod/{...}
Paths inside them are relative, so they work in place:
just run-relay preview # or: just run-bp preview
# equivalently
dugite-node run \
--config config/preview/config.json \
--topology config/preview/topology.json \
--database-path ./db-preview \
--socket-path ./node.sock \
--host-addr 0.0.0.0 --port 3001
The sections below show minimal hand-written equivalents if you would rather build your own.
Connecting to Mainnet
Create a config-mainnet.json:
{
"Network": "Mainnet",
"NetworkMagic": 764824073
}
Create a topology-mainnet.json:
{
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 },
{ "address": "backbone.mainnet.emurgornd.com", "port": 3001 }
],
"localRoots": [],
"publicRoots": [
{
"accessPoints": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 0
}
Run the node:
dugite-node run \
--config config-mainnet.json \
--topology topology-mainnet.json \
--database-path ./db-mainnet \
--socket-path ./node-mainnet.sock \
--host-addr 0.0.0.0 \
--port 3001
Tip: For a faster initial mainnet sync, consider using Mithril snapshot import first.
Note: A config with no genesis files, like the minimal one above, starts on built-in default protocol parameters rather than mainnet's. Point at the real genesis files —
config/mainnet/has them — before syncing for real.
Connecting to Preview Testnet
Note: Preview testnet is at Protocol Version 11 (PV11). Peers running cardano-node 10.x will reject the connection with a version mismatch. Use cardano-node 11.0.1+ for any preview peer or soak rig.
Create a config-preview.json. The shipped config/preview/config.json sets
ExperimentalHardForksEnabled: true, which makes the node signal ProtVer 12 0
and accept on-chain protocol versions up to 12, rather than the default
ProtVer 11 0 / max 11:
{
"Network": "Testnet",
"NetworkMagic": 2,
"ExperimentalHardForksEnabled": true
}
Create a topology-preview.json:
{
"bootstrapPeers": [
{ "address": "preview-node.play.dev.cardano.org", "port": 3001 }
],
"localRoots": [
{ "accessPoints": [], "advertise": false, "trustable": false, "valency": 1 }
],
"publicRoots": [
{
"accessPoints": [
{ "address": "preview-node.play.dev.cardano.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 102729600
}
Run the node:
dugite-node run \
--config config-preview.json \
--topology topology-preview.json \
--database-path ./db-preview \
--socket-path ./node-preview.sock \
--host-addr 0.0.0.0 \
--port 3001
Connecting to Preprod Testnet
Create a config-preprod.json:
{
"Network": "Testnet",
"NetworkMagic": 1
}
Create a topology-preprod.json:
{
"bootstrapPeers": [
{ "address": "preprod-node.play.dev.cardano.org", "port": 3001 }
],
"localRoots": [
{ "accessPoints": [], "advertise": false, "trustable": false, "valency": 1 }
],
"publicRoots": [
{
"accessPoints": [
{ "address": "preprod-node.play.dev.cardano.org", "port": 3001 }
],
"advertise": false
}
],
"useLedgerAfterSlot": 76723200
}
Run the node:
dugite-node run \
--config config-preprod.json \
--topology topology-preprod.json \
--database-path ./db-preprod \
--socket-path ./node-preprod.sock \
--host-addr 0.0.0.0 \
--port 3001
Official Configuration Files
Official configuration and topology files for each network are maintained in the Cardano Operations Book:
- Preview: book.world.dev.cardano.org/environments/preview/
- Preprod: book.world.dev.cardano.org/environments/preprod/
- Mainnet: book.world.dev.cardano.org/environments/mainnet/
These include the full genesis files (Byron, Shelley, Alonzo, Conway) required for complete protocol parameter initialization.
Using the CLI with Different Networks
When querying a node connected to a testnet, pass the --testnet-magic flag to the CLI:
# Preview
dugite-cli query tip --socket-path ./node-preview.sock --testnet-magic 2
# Preprod
dugite-cli query tip --socket-path ./node-preprod.sock --testnet-magic 1
# Mainnet (default, --testnet-magic not needed)
dugite-cli query tip --socket-path ./node-mainnet.sock
Multiple Nodes
You can run multiple Dugite instances on the same machine, but every one of the four per-node resources has to be distinct: the N2N port, the N2C socket path, the database directory, and the Prometheus metrics port.
# Preview on port 3001
dugite-node run --port 3001 --database-path ./db-preview \
--socket-path ./preview.sock --metrics-port 12796 ...
# Preprod on port 3002
dugite-node run --port 3002 --database-path ./db-preprod \
--socket-path ./preprod.sock --metrics-port 12799 ...
The metrics port is the one that is easy to miss, because it does not appear on
the command line unless you put it there. Dugite's default is 12796
(deliberately offset from cardano-node's 12798 so the two can coexist), so two
Dugite nodes started without --metrics-port or a MetricsPort config field
will collide on it. The shipped configs pre-assign distinct ports: mainnet 12800,
preview 12796, preprod 12799.
By default a metrics bind failure is logged and the node keeps running. Pass
--require-metrics to make it a fatal startup error instead, which is what you
want under a supervisor.
Each database directory is protected by an exclusive flock on <db>/lock, so
pointing two nodes at the same --database-path fails fast and names the pid
already holding it rather than corrupting the database.
Mithril Snapshot Import
Syncing a Cardano node from genesis can take a very long time. Dugite supports importing Mithril-certified snapshots of the immutable database to drastically reduce initial sync time.
How It Works
Mithril is a stake-based threshold multi-signature scheme that produces certified snapshots of the Cardano immutable database. These snapshots are verified by Mithril signers (stake pool operators) and made available through Mithril aggregator endpoints.
The import process:
- Queries the Mithril aggregator for the latest available Cardano Database (V2) snapshot
- Verifies the STM certificate chain back to the network's pinned genesis verification key
- Downloads the snapshot archive (compressed with zstandard)
- Extracts the cardano-node chunk files
- Parses each block using Dugite's in-house multi-era CBOR decoder
- Bulk-imports blocks into Dugite's ImmutableDB (append-only chunk files)
- Imports the ancillary archive (the Haskell ledger state at the immutable tip) unless disabled
Usage
dugite-node mithril-import \
--network-magic <magic> \
--database-path <path>
Or via the justfile wrapper, which builds the release binary if needed and picks
the magic and database path for you (./db-<network>):
just mithril-import preview # preview | preprod | mainnet
Arguments
| Argument | Default | Description |
|---|---|---|
--network-magic | 764824073 | Network magic (764824073=mainnet, 2=preview, 1=preprod) |
--database-path | db | Path to the database directory |
--temp-dir | system temp | Temporary directory for download and extraction |
--include-ancillary | true | Download and import the Mithril ancillary archive (Haskell ledger state at the immutable tip). When enabled, bootstrap drops from multi-hour to ~15 minutes. See Mithril Ancillary — Trust Model |
--no-include-ancillary | — | Skip the ancillary download. Equivalent to --include-ancillary=false, provided because the negated form reads better in scripts. Conflicts with --include-ancillary |
--allow-stale-pparams | false | Continue even if the ancillary download fails — falls back to genesis-default protocol parameters at the imported tip (issue #335). NOT recommended for production |
--mithril-genesis-vkey | pinned | Override the Mithril genesis verification key, for private networks. Must be a JSON hex-encoded Ed25519 verification key string |
--skip-certificate-verification | false | UNSAFE, testing only. Trust the snapshot digest from the aggregator without verifying the certificate chain |
All the standard logging flags (--log-output, --log-format, --log-level, …)
also apply to mithril-import. See Logging.
Examples
Mainnet:
dugite-node mithril-import \
--network-magic 764824073 \
--database-path ./db-mainnet
Preview testnet:
dugite-node mithril-import \
--network-magic 2 \
--database-path ./db-preview
Preprod testnet:
dugite-node mithril-import \
--network-magic 1 \
--database-path ./db-preprod
Mithril Aggregator Endpoints
Dugite automatically selects the correct aggregator for each network:
| Network | Aggregator URL |
|---|---|
| Mainnet | https://aggregator.release-mainnet.api.mithril.network/aggregator |
| Preview | https://aggregator.pre-release-preview.api.mithril.network/aggregator |
| Preprod | https://aggregator.release-preprod.api.mithril.network/aggregator |
Interruption and Re-Runs
An interrupted import restarts from scratch. The temporary download directory is cleared at the start of every run so chunk files from a different snapshot cannot leak into the new one, and the Mithril client re-fetches everything. There is no partial-download resume.
Re-running the import is also destructive to the target database: before
moving the new chunk files into place, mithril-import deletes any existing
<database-path>/immutable/ directory, plus the Haskell ledger/ directory and
stale ledger snapshots. It does not take the database lock while doing so.
Stop the node before importing.
mithril-importwrites to the database directory with plain filesystem operations and does not acquire the<db>/lockflock thatdugite-node runholds, so it will not detect a running node and will delete the immutable directory out from under it. Stop the node with SIGTERM first, and point--database-pathat a fresh directory if you want to keep the old one.
Download concurrency is tunable via DUGITE_MITHRIL_DOWNLOAD_PARALLELISM
(per-immutable-file, default 20, clamped to 1–32).
After Import
Once the import completes, start the node normally. It will detect the imported blocks and resume syncing from where the snapshot left off:
dugite-node run \
--config config/mainnet/config.json \
--topology config/mainnet/topology.json \
--database-path ./db-mainnet \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001
Stop the node with SIGTERM, never kill -9 — a hard kill risks the active
ImmutableDB chunk's index. See
Stopping the Node.
When a Re-Import Is Required
The on-disk ledger snapshot carries a SNAPSHOT_VERSION. When a release bumps
it, the old snapshot is rejected on load and the node replays chunks from the
last compatible point; when the import format itself changed, a full
mithril-import is required instead.
| Upgrading from | Action needed |
|---|---|
| Before v2.1.0 | Full mithril-import required. Pre-v2.1.0 imports discarded governance roots (Proposals.pRoots), which silently corrupts reward calculation — see issue #898. Re-import; a replay will not repair it |
| v2.1.0 – v2.2.x | Chunk replay on first restart (SNAPSHOT_VERSION reached 31 in v2.3.0) |
| v2.3.0 or later | Drop-in. SNAPSHOT_VERSION has been unchanged at 31 since v2.3.0, including the current v2.4.3 release. No re-import and no re-sync |
v2.4.0 additionally introduced two new files inside the database directory —
lock and immutable/clean. Both are created automatically; no operator action
is required.
Disk Space Requirements
Mithril snapshots are large. Approximate sizes (which grow over time):
| Network | Compressed Archive | Extracted | Final DB |
|---|---|---|---|
| Mainnet | ~60-90 GB | ~120-180 GB | ~90-140 GB |
| Preview | ~5-10 GB | ~10-20 GB | ~8-15 GB |
| Preprod | ~15-25 GB | ~30-50 GB | ~20-35 GB |
The temporary directory needs enough space for both the compressed archive and the extracted files. After import, temporary files are automatically cleaned up.
Note: Ensure you have sufficient disk space before starting the import. The
--temp-dirflag can be used to direct temporary files to a different volume if needed.
Mithril Ancillary Import — Trust Model
The Mithril aggregator publishes two artefacts per snapshot:
- Main archive — the certified ImmutableDB chunk files (
immutable/*.{chunk,primary,secondary}). - Ancillary archive — the serialised Haskell
ExtLedgerStateat the immutable tip (ledger/<slot>/), plus the partial tip chunkN+1.
Dugite consumes both: the main archive populates ImmutableDB, and the ancillary archive lets the node skip the multi-hour chain-from-genesis replay normally needed to rebuild ledger state.
On disk the handoff is a two-stage one:
mithril-importmoves the unpackedledger/directory to<database-path>/haskell-ledger/.- The next
dugite-node runpicks the highest-numbered slot subdirectory underhaskell-ledger/, decodes it, writes a nativeledger-snapshot.bin, and deleteshaskell-ledger/once consumed. If the decode fails the node logs a warning and falls back to chain replay rather than aborting.
If ancillary is requested but no ledger/ directory was unpacked, the import is
a hard error — precisely so the node cannot silently come up on
genesis-default protocol parameters (issue #335). --allow-stale-pparams
downgrades that to a warning.
This document records the trust model, the operator-exposure decision, and the verification harness used to confirm byte-exactness.
Why ancillary matters
Without ancillary, after mithril-import returns the node still has to replay every certified block through its own validator to rebuild:
- The UTxO set
- Pool, DRep, committee and proposal state
- Treasury, reserves, fees, deposits
- All five Praos nonces (
evolving,candidate,epoch,lab,last_epoch_block) - Operational-certificate counters per pool
- Stake distribution + mark/set/go snapshots
- Pending reward updates and MIR deltas
On mainnet that replay takes multi-hour today (typically 10+ hours on commodity hardware). Importing the ancillary directly drops cold-start to ~15 minutes of decode + LSM bulk-load.
Trust model
The ancillary archive is signed and certified by Mithril's threshold multi-signature scheme using the same ≥ 2/3 stake-weighted aggregator key that signs the immutable chunks. Concretely:
- Dugite drives the official
mithril-clientSDK against the aggregator's Cardano Database (V2) API:list()for the snapshot set,get(hash)for the detail (Merkle root, immutables, ancillary), then a download that unpacks the standard cardano-nodedb/layout —immutable/for chunk files (0..=N, plus the ancillary tip chunkN+1) andledger/for the Haskell ledger-state snapshot. - The ancillary archive carries its own Ed25519 manifest signature. Dugite supplies the network's pinned ancillary verification key to the client builder via
set_ancillary_verification_key, so the SDK verifies that signature during unpack. Verification is therefore performed insidemithril-client, not by hand inmithril.rs. - If the network has no pinned ancillary key, the import logs a warning that the ancillary signature cannot be verified and proceeds — it does not abort.
The verification keys are pinned in crates/dugite-node/src/mithril.rs:
| Constant | Selected by |
|---|---|
MAINNET_GENESIS_VKEY / MAINNET_ANCILLARY_VKEY | network magic 764824073 |
PREVIEW_GENESIS_VKEY / PREVIEW_ANCILLARY_VKEY | network magic 2 |
PREPROD_GENESIS_VKEY / PREPROD_ANCILLARY_VKEY | network magic 1 |
The genesis key is resolved by genesis_verification_key() and the ancillary key
by ancillary_verification_key_hex(). --mithril-genesis-vkey overrides the
genesis key for private networks; there is no equivalent override for the
ancillary key.
The aggregator's full STM certificate chain is also verified — verify_chain() walks every certificate's multi-signature back to the genesis certificate — unless --skip-certificate-verification is set. This is identical to the trust posture used for the main snapshot.
What we trust when we accept the ancillary
We trust that a Haskell cardano-node implementation honestly computed the ledger state at the certified immutable tip and published it. We do not re-validate the state against our own from-genesis replay before using it.
This is a stronger trust assumption than the main snapshot alone: the main snapshot is a certified statement that "this chain is canonical and ≥ 2/3 of stake agrees"; the ancillary is a stronger statement that "the state derived by Haskell from this chain is also correct."
What we do not trust
We do not trust the ancillary's CBOR encoder to be implementation-independent: dugite's runtime types differ from Haskell's HFC-telescope / HKD-parameterised types, so the adapter (LedgerState::from_haskell_snapshot, crates/dugite-ledger/src/state/mod.rs:697) explicitly converts every field. Any new era will need adapter coverage before the ancillary path will work for that era.
Operator-exposure decision
The flag --include-ancillary is default-on. Passing --no-include-ancillary skips the ancillary download and falls back to chain-from-genesis replay.
Rationale:
- Most end-users prefer fast bootstrap and accept the certified-ancillary trust posture (which is the same posture used for the main snapshot's stake-weighted certification).
- The pre-ancillary import path (chunk-only) historically caused issue #335 — stale genesis-default protocol parameters at the imported tip — because the node had no way to learn current PParams without replay. The ancillary path resolves this by providing live PParams directly.
- Operators who need byte-exact verification of dugite's own ledger derivation against the Haskell reference should run two imports — one with
--include-ancillaryand one with--no-include-ancillary(waiting for the replay to complete) — then diff the twoledger-snapshot.binfiles viadugite-node verify-ledger-snapshot.
Verification harness (issue #670 acceptance)
dugite-node verify-ledger-snapshot --left <path> --right <path> performs a semantic byte-exact comparison of two ledger snapshots. The harness reports any field-level mismatch with full diagnostic detail and exits non-zero on failure.
Each <path> may be either a ledger-snapshot.bin file or a database directory containing one.
Acceptance procedure for a new era / boundary
-
Build two databases:
dugite-node mithril-import \ --network-magic 2 \ --database-path ./db-preview-ancillary \ --include-ancillary dugite-node mithril-import \ --network-magic 2 \ --database-path ./db-preview-replay \ --no-include-ancillary -
Replay the no-ancillary database by running
dugite-node runagainst it until the ledger state catches up to the same anchor as the ancillary database. The replay is the slow path the ancillary is designed to avoid; allow multi-hour wall time. -
Compare:
dugite-node verify-ledger-snapshot \ --left ./db-preview-ancillary \ --right ./db-preview-replay -
Expected output on PASS:
PASS — snapshots are semantically equal -
On FAIL, the harness prints one line per differing field. Use those entries to locate the divergence — for example a
governance.proposalsmismatch points to the Conway governance decoder, asnapshots.setmismatch points to mark/set/go snapshot semantics, etc.
Acceptance status
The harness must PASS for at least one preview boundary AND one preprod boundary before the ancillary path is considered fully verified for a given era.
Prior to commits in 2026 Q2 (issues #438, #481, #624, #626, #678, #685) the from-genesis replay did not match the Haskell ancillary at all boundaries — the harness reported drift in pot fields (treasury, reserves, epoch_fees) cascading from a missing Babbage→Conway PPUP path that left the on-chain protocol version stuck at 8 in dugite while the canonical chain ran at 9. With those resolved, a preview-mainnet-style chunk replay through to the mithril anchor now reproduces the Haskell ledger byte-exact, including:
- Pots (treasury, reserves, fees, deposits, donation)
- All five Praos nonces
- DRep + committee + proposal + vote state
- Mark/set/go snapshots + ssFee
- bprev block production counters
- Stake distribution + per-credential deposits
Any remaining open epoch-diff issue is a blocker for re-claiming the gate; consult the project tracker before signing off a new release that touches era-translation, governance enactment, or PPUP semantics.
Note also that the adapter is era-coupled: dugite's runtime types differ from Haskell's HFC-telescope / HKD-parameterised types, so each new era needs explicit adapter coverage in from_haskell_snapshot before the ancillary path will work for it. A new era landing upstream is a reason to re-run this harness, not to assume it still holds.
Code references
Line numbers are deliberately omitted below — search for the named symbol instead, since these files change often.
| Concern | Where |
|---|---|
--include-ancillary / --no-include-ancillary CLI flags | crates/dugite-node/src/main.rs (MithrilImportArgs) |
| Import driver (list → verify chain → download → place) | crates/dugite-node/src/mithril.rs (import_snapshot) |
| Per-network genesis verification keys | crates/dugite-node/src/mithril.rs (genesis_verification_key) |
| Per-network ancillary verification keys | crates/dugite-node/src/mithril.rs (ancillary_verification_key_hex) |
| Aggregator endpoints | crates/dugite-node/src/mithril.rs (aggregator_url) |
| Haskell snapshot decoder | crates/dugite-serialization/src/haskell_snapshot/ (decode_state_file) |
| Adapter (Haskell → dugite types) | crates/dugite-ledger/src/state/mod.rs (LedgerState::from_haskell_snapshot) |
| Node startup integration | crates/dugite-node/src/node/mod.rs (search haskell-ledger) |
verify-ledger-snapshot subcommand | crates/dugite-node/src/main.rs (VerifyLedgerSnapshot) |
| Comparison harness module | crates/dugite-node/src/verify_snapshot.rs |
Related issues
- #670 — This issue. Adds the explicit CLI flag, documents the trust model and operator-exposure decision, and ships the byte-exact verification harness.
- #335 — Stale genesis-default protocol parameters when ancillary was skipped (root cause for making ancillary default-on).
- #626 — Residual +297K-ADA drift at preview boundary 3→4 (resolved; PPUP timing aligned with Haskell HFC tick).
- #624 — Pre-Conway PPUP decoder fix that closed earlier boundary drifts.
- #678 — Conway treasury-value check incorrectly gated; resolved by mode-gating on
ValidateAllto match HaskellApplySTSOpts.asoValidation. - #685 — Missing PPUP application at Babbage→Conway era boundary +
prev_ppcaptured AFTERratify_proposals_impl. Both fixed; replay now byte-exact through the originally-failing preview slot 76172461 and past. - #516 — Single-use channel constraint workaround (unrelated to ancillary but referenced from the same lifecycle code).
Logging
Dugite uses the tracing ecosystem for structured logging. It supports multiple output targets, structured and human-readable formats, log rotation for file output, and fine-grained level control.
Output Formats
Dugite supports two log formats, selectable via the --log-format flag:
Text (default)
Human-readable compact output with timestamps, level, target module, and structured fields:
dugite-node run --log-format text ...
2026-03-12T12:34:56.789Z INFO dugite_node::node: Syncing progress="95.42%" epoch=512 block=11283746 tip=11300000 remaining=16254 speed="312 blk/s" utxos=15234892
2026-03-12T12:34:56.790Z INFO dugite_node::node: Peer connected peer=1.2.3.4:3001 rtt_ms=42
JSON
Structured JSON output, one object per line. Ideal for log aggregation systems (ELK, Loki, Datadog):
dugite-node run --log-format json ...
{"timestamp":"2026-03-12T12:34:56.789Z","level":"INFO","target":"dugite_node::node","fields":{"message":"Syncing","progress":"95.42%","epoch":512,"block":11283746}}
Output Targets
Dugite can log to one or more output targets simultaneously using the --log-output flag. You can specify this flag multiple times to enable multiple targets:
# Stdout only (default)
dugite-node run --log-output stdout ...
# File only
dugite-node run --log-output file ...
# Both stdout and file
dugite-node run --log-output stdout --log-output file ...
# Systemd journal (requires journald feature)
dugite-node run --log-output journald ...
Stdout
The default output target. Logs are written to standard output with ANSI color codes when the output is a terminal. Colors can be disabled with --log-no-color.
File
Logs are written to rotating log files in the directory specified by --log-dir (default: logs/). The rotation strategy is configured with --log-file-rotation:
| Strategy | Description |
|---|---|
daily | Rotate log files daily (default) |
hourly | Rotate log files every hour |
never | Write to a single dugite.log file with no rotation |
dugite-node run \
--log-output file \
--log-dir /var/log/dugite \
--log-file-rotation daily \
...
File output uses non-blocking I/O with buffered writes. The buffer is flushed automatically on shutdown — which is one more reason to stop the node with SIGTERM rather than kill -9.
Log files are named dugite.log (with a date/hour suffix under daily /
hourly rotation). Note that --log-retention-days is accepted but the cleanup
sweep is not currently wired into the running node, so old files accumulate
until you rotate them out yourself (logrotate, a cron job, or a tmpfiles rule).
Journald
Native systemd journal integration. This requires building Dugite with the journald feature, which lives on the dugite-node crate:
cargo build --release -p dugite-node --features journald
Requesting --log-output journald from a binary built without the feature is a hard startup error, not a silent downgrade to stdout.
Then run with:
dugite-node run --log-output journald ...
View logs with journalctl:
journalctl -u dugite-node -f
journalctl -u dugite-node --since "1 hour ago"
Log Levels
Verbosity is resolved with this precedence, highest first:
RUST_LOGenvironment variable--log-levelCLI flagLogDirectiveconfig fieldMinSeverityconfig field
# Via CLI flag
dugite-node run --log-level debug ...
# Via environment variable (takes priority)
RUST_LOG=debug dugite-node run ...
The two config fields are applied by a live filter reload immediately after the
config file is parsed, and only when neither RUST_LOG nor --log-level is
set — so a CLI or environment override is never clobbered by the file. See
Configuration → Log Level Control.
MinSeverity uses cardano-node's syslog vocabulary, which is wider than
tracing's. It is translated rather than passed through: Notice → info, and
Critical / Alert / Emergency → error. An unrecognised value falls back to
info. Use LogDirective when you need per-target control — it is handed to
EnvFilter unchanged.
Available levels (from most to least verbose):
| Level | Description |
|---|---|
trace | Very detailed internal diagnostics |
debug | Internal operations: genesis loading, storage ops, network handshakes, epoch transitions |
info | Operator-relevant events: sync progress, peer connections, block production (default) |
warn | Potential issues: stale snapshots, replay failures |
error | Errors that may affect node operation |
Per-Crate Filtering
Use RUST_LOG for fine-grained control over which components produce output:
# Debug only for specific crates
RUST_LOG=dugite_network=debug,dugite_consensus=debug dugite-node run ...
# Trace storage operations, debug everything else
RUST_LOG=dugite_storage=trace,debug dugite-node run ...
# Silence noisy crates
RUST_LOG=info,dugite_network=warn dugite-node run ...
CLI Reference
The logging flags are shared by every subcommand that does work: run,
mithril-import, dump-snapshot, verify-ledger-snapshot, and
snapshot-convert. (db info does not initialise the subscriber and takes no
logging flags.)
| Flag | Default | Description |
|---|---|---|
--log-output | stdout | Log output target: stdout, file, or journald (aliases: journal, systemd). Can be specified multiple times. Values are case-insensitive |
--log-format | text | Log format: text (alias plain) or json (structured) |
--log-level | info | Log level: trace, debug, info, warn, error. Overridden by RUST_LOG |
--log-dir | logs | Directory for log files (used with --log-output file) |
--log-file-rotation | daily | Log file rotation: daily, hourly, or never (alias none) |
--log-no-color | false | Disable ANSI colors in stdout output. Colors are auto-disabled anyway when stdout is not a terminal |
--log-retention-days | 7 | Accepted but currently inert — no cleanup task is wired into the running node |
--stdout-overflow | drop | Channel-full policy for the non-blocking stdout writer. drop (alias lossy) keeps the hot path unblocked and counts dropped lines; block (alias lossless) parks the producer until the writer drains |
An unrecognised value for any of these is a startup error naming the valid set, not a silent fallback.
Non-blocking output and --stdout-overflow
Every tracing call hands its line to a background writer thread over a bounded
channel rather than performing a synchronous write(2) on the emitting tokio
worker. The default drop policy means that under a genuine log flood, lines
are discarded rather than stalling block application — which is the right
trade-off in production.
Use --stdout-overflow block for development, CI, or forensic capture where
every line must survive. It re-introduces blocking on the hot path, so it
defeats the point of the non-blocking writer under real overload.
Runtime Log Verbosity Reload (SIGHUP)
Dugite supports changing per-subsystem log verbosity at runtime without restarting the node. This is useful for debugging a specific issue (for example, enabling trace logging for the network layer) without disrupting ongoing block production or sync.
Workflow:
-
Edit the node configuration file and add (or update) the
LogDirectivefield:{ "LogDirective": "info,dugite_network=trace,dugite_consensus=debug" }The value accepts any
RUST_LOG-compatible directive, including*=debug,trace, or per-module overrides likedugite_ledger=warn. -
Send
SIGHUPto the running node:kill -HUP $(pgrep -x dugite-node)The node re-reads the config file. If
LogDirectiveis present and valid, the filter is reloaded immediately across every output target and the change is logged.The directive is parsed before any filter handle is touched, so an invalid string leaves the previous filter fully intact rather than half-applied.
-
To restore the original level, remove
LogDirectivefrom the config and send SIGHUP again, or set it back to"info".
Two details worth knowing, because they differ between startup and SIGHUP:
- At startup,
LogDirective/MinSeverityare applied only when neitherRUST_LOGnor--log-levelis set, so an explicit operator override is never clobbered by the file. - On SIGHUP, they are applied unconditionally, and the reload uses the
directive directly rather than re-consulting
RUST_LOG. A SIGHUP therefore does override a level you set on the command line or in the environment.
The log filter is only touched when at least one hot-reloadable field actually
changed. A SIGHUP against an unmodified config file is a no-op, logged as
config_reload: no fields changed.
SIGHUP also reloads the topology and the hot-reloadable peer-governor targets. See Live Reload for the full field partition.
Production Recommendations
For production deployments with log aggregation:
dugite-node run \
--log-output file \
--log-output journald \
--log-format json \
--log-dir /var/log/dugite \
--log-file-rotation daily \
...
This configuration:
- Writes structured JSON logs to systemd journal for
journalctlintegration - Writes rotated JSON log files for archival and ingestion by log aggregators
- JSON format ensures all structured fields are machine-parseable
For human operators monitoring the console:
dugite-node run --log-output stdout --log-format text ...
For containerized deployments (Docker, Kubernetes), stdout with JSON is ideal since the container runtime captures output and log drivers can parse the structured format:
dugite-node run --log-output stdout --log-format json ...
Monitoring
Dugite provides two complementary monitoring tools: a terminal dashboard (dugite-monitor) for quick at-a-glance status, and a Prometheus-compatible metrics endpoint for production alerting and dashboards.
Terminal Dashboard (dugite-monitor)
dugite-monitor is a standalone binary that renders a real-time status dashboard in the terminal by polling the node's Prometheus endpoint. It requires no external infrastructure and works over SSH.

# Auto-discover a running dugite-node and attach
dugite-monitor
# Monitor a specific endpoint (skips discovery)
dugite-monitor --metrics-url http://192.168.1.100:12796/metrics
# Pin the epoch length instead of auto-detecting it from dugite_network_magic
dugite-monitor --network-magic 2
# Show disk usage for the node's database volume in the Resources panel
dugite-monitor --db-path ./db-preview
When --metrics-url is omitted, dugite-monitor enumerates running dugite-node processes (via sysinfo + netstat2) and probes their /metrics endpoints. One node found: it attaches silently. Multiple: a selection dialog appears. None: it falls back to http://localhost:12798/metrics.
Careful: that fallback (12798) is not the node's default metrics port (12796 — see Which port? below). The fallback only matters when discovery finds nothing, in which case there is usually no node to monitor anyway. If the monitor reports no data, pass
--metrics-urlexplicitly with the port your config actually pins.
The dashboard displays five panels:
- Node — role, network, version, era, uptime
- Chain — epoch progress bar, block/slot/tip metrics, density, forks, tx counts
- Connections — P2P state, inbound/outbound, cold/warm/hot, uni/bi/duplex counts
- Resources — CPU %, live memory, RSS memory (plus disk when
--db-pathis set) - Peers — RTT bands (0-50 ms, 50-100 ms, 100-200 ms, 200 ms+), min/avg/max RTT
Metrics are polled once per second. The interval is a compile-time constant — there is no flag to change it.
| Key | Action |
|---|---|
q / Esc | Quit |
t | Cycle theme |
r | Force-refresh metrics |
s | Switch to a different node |
h / ? | Toggle help overlay |
Prometheus Metrics Endpoint
Dugite exposes a Prometheus-compatible metrics endpoint for monitoring node health and sync progress.
Metrics Endpoint
The metrics server responds to any unrecognised HTTP path with Prometheus exposition format metrics:
http://localhost:12796/metrics
Which port?
The built-in fallback is 12796, deliberately offset from cardano-node's 12798 so a dugite node and a Haskell node can co-exist on one host. The shipped per-network configs each pin a port explicitly, so in practice the port comes from the config file:
| Config | MetricsPort |
|---|---|
config/preview/config.json | 12796 |
config/preprod/config.json | 12799 |
config/mainnet/config.json | 12800 |
| (field absent) | 12796 |
Resolution order, highest priority first:
--no-metrics→ server disabled.--metrics-port <PORT>→ explicit operator override (wins even overTurnOnLogMetrics=false).TurnOnLogMetrics: falsein the config JSON → server disabled (master off-switch, matching cardano-node).MetricsPortin the config JSON.- Built-in default 12796.
Pass --require-metrics to make a bind failure a fatal startup error instead of a logged warning.
The examples below use 12796. Substitute your network's port from the table above.
Example response:
# HELP dugite_blocks_received_total Total blocks received from peers
# TYPE dugite_blocks_received_total counter
dugite_blocks_received_total 1523847
# HELP dugite_blocks_applied_total Total blocks applied to ledger
# TYPE dugite_blocks_applied_total counter
dugite_blocks_applied_total 1523845
# HELP dugite_slot_number Current slot number
# TYPE dugite_slot_number gauge
dugite_slot_number 142857392
# HELP dugite_block_number Current block number
# TYPE dugite_block_number gauge
dugite_block_number 11283746
# HELP dugite_epoch_number Current epoch number
# TYPE dugite_epoch_number gauge
dugite_epoch_number 512
# HELP dugite_sync_progress_percent Chain sync progress (0-10000, divide by 100 for %)
# TYPE dugite_sync_progress_percent gauge
dugite_sync_progress_percent 9542
# HELP dugite_utxo_count Number of entries in the UTxO set
# TYPE dugite_utxo_count gauge
dugite_utxo_count 15234892
# HELP dugite_mempool_tx_count Number of transactions in the mempool
# TYPE dugite_mempool_tx_count gauge
dugite_mempool_tx_count 42
# HELP dugite_peers_connected Number of connected peers
# TYPE dugite_peers_connected gauge
dugite_peers_connected 8
Health Endpoint
The metrics server exposes a /health endpoint for monitoring node status:
GET http://localhost:12796/health
Always returns 200 OK. The status field carries the verdict:
- healthy — sync progress >= 99.9%
- syncing — actively catching up to chain tip
- stalled — no blocks received for > 5 minutes AND sync < 99%
{
"status": "healthy",
"uptime_seconds": 3421,
"slot_number": 142857392,
"block_number": 11283746,
"epoch_number": 512,
"sync_progress": 99.95,
"peers_connected": 8,
"last_block_received_at": "2026-03-14T12:34:56.789Z"
}
last_block_received_at is null until the first block arrives.
Readiness Endpoint
For Kubernetes readiness probes:
GET http://localhost:12796/ready
Returns 200 OK when sync_progress >= 99.9%, 503 Service Unavailable otherwise:
{"ready": true}
or:
{"ready": false, "sync_progress": 75.42}
Liveness Endpoint
For Kubernetes liveness probes — this is the one that should restart a wedged pod:
GET http://localhost:12796/live
Returns 200 OK when a block has been applied within --liveness-threshold-secs (default 600), or when no block has arrived yet but the node has been up for less than that window (warm-up grace). Otherwise 503:
{"alive": true, "threshold_secs": 600}
{"alive": false, "threshold_secs": 600, "last_block_received_at": "2026-03-14T12:34:56.789Z"}
Set --liveness-threshold-secs 0 to make /live always return 200.
/readytracks sync progress;/livetracks forward progress. A node that is 40% synced but applying blocks is correctly not-ready and alive. Wiring a liveness probe to/readywill restart-loop a node that is merely still syncing.
EKG Compatibility Endpoint
GET http://localhost:12796/ekg
Returns the nested-object JSON layout that cardano-node's EKG (System.Remote.Monitoring) exposes, so legacy gLiveView / CNTools dashboards that poll port 12788 can be pointed at dugite unmodified.
Available Metrics
Counters
| Metric | Description |
|---|---|
dugite_blocks_received_total | Total blocks received from peers |
dugite_blocks_applied_total | Total blocks successfully applied to the ledger |
dugite_transactions_received_total | Total transactions received |
dugite_transactions_validated_total | Total transactions validated |
dugite_transactions_rejected_total | Total transactions rejected |
dugite_rollback_count_total | Total number of chain rollbacks |
dugite_block_apply_failures_total | Blocks that failed to apply to the ledger |
dugite_fetched_blocks_not_connecting_total | Fetched blocks that did not connect to the current chain |
dugite_header_full_validations_total | Headers that went through full crypto validation |
dugite_header_validation_failures_total | Headers that failed validation |
dugite_blocks_forged_total | Total blocks forged by this node |
dugite_leader_checks_total | Total VRF leader checks performed |
dugite_leader_checks_not_elected_total | Leader checks where node was not elected |
dugite_forge_failures_total | Block forge attempts that failed |
dugite_blocks_announced_total | Blocks successfully announced to peers |
dugite_forge_race_lost_total | Forged blocks that lost the race to another pool |
dugite_forge_slot_battles_total | Slot battles entered |
dugite_forge_announce_no_subscribers_total | Forged blocks with no peer to announce to |
dugite_n2n_connections_total | Total N2N (peer-to-peer) connections accepted |
dugite_n2c_connections_total | Total N2C (client) connections accepted |
dugite_apply_mode_reapply_total | Blocks applied in trust-consensus reapply mode |
dugite_apply_mode_validate_all_total | Blocks applied with full validation |
dugite_blockfetch_rx_bytes_total | Bytes received over BlockFetch |
dugite_blockfetch_busy_us_total | Microseconds BlockFetch spent busy |
dugite_blockfetch_send_blocked_us_total | Microseconds BlockFetch stalled on send |
dugite_blockfetch_idle_no_headers_total | BlockFetch idle periods caused by header starvation |
dugite_snapshot_enqueued_total | Ledger snapshots enqueued |
dugite_snapshot_skipped_busy_total | Snapshots skipped because the worker was busy |
dugite_snapshot_failed_total | Snapshot attempts that failed |
dugite_utxo_flush_failed_total | UTxO store flush failures |
dugite_validation_errors_total{error="..."} | Transaction validation errors, broken down by error type |
dugite_protocol_errors_total{error="..."} | Protocol-level errors by type (e.g. handshake failures, connection errors) |
dugite_config_reload_total{result="..."} | SIGHUP-triggered config reloads by result |
Gauges
Chain and sync
| Metric | Description |
|---|---|
dugite_sync_progress_percent | Chain sync progress (0-10000; divide by 100 for percentage) |
dugite_slot_number | Current slot number |
dugite_block_number | Current block number |
dugite_epoch_number | Current epoch number |
dugite_slot_in_epoch | Offset of the current slot within the epoch (era-aware) |
dugite_epoch_length | Slots per epoch for the current epoch (era-aware) |
dugite_slot_length_ms | Slot duration in ms from the active Shelley genesis |
dugite_active_slots_coeff_x1000 | Praos f scaled by 1000 (200 = f=0.20) |
dugite_era | Ledger era index: 0=Byron, 1=Shelley, 2=Allegra, 3=Mary, 4=Alonzo, 5=Babbage, 6=Conway, 7=Dijkstra |
dugite_protocol_major_version / dugite_protocol_minor_version | Active protocol version |
dugite_network_magic | 764824073=mainnet, 2=preview, 1=preprod |
dugite_tip_age_seconds | Seconds since the tip slot time |
dugite_chainsync_idle_seconds | Seconds since last ChainSync RollForward event |
dugite_max_peer_tip_slot | Highest tip slot advertised by any peer |
dugite_ledger_replay_duration_seconds | Duration of last ledger replay in seconds |
dugite_gsm_state | Genesis State Machine state: 0=PreSyncing, 1=Syncing, 2=CaughtUp |
dugite_consensus_mode | 0=Praos, 1=Ouroboros Genesis |
dugite_loe_tip_slot | Limit on Eagerness tip slot published to chain selection |
dugite_utxo_count | Number of entries in the UTxO set |
Peers and connections
| Metric | Description |
|---|---|
dugite_peers_connected | Number of connected peers |
dugite_peers_cold | Number of cold (known but unconnected) peers |
dugite_peers_warm | Number of warm (connected, not syncing) peers |
dugite_peers_hot | Number of hot (actively syncing) peers |
dugite_peers_inbound / dugite_peers_outbound / dugite_peers_duplex | Peers by connection direction |
dugite_conn_inbound / dugite_conn_outbound / dugite_conn_duplex / dugite_conn_full_duplex / dugite_conn_unidirectional / dugite_conn_terminating | Connection-manager state counts |
dugite_n2n_connections_active | Currently active N2N connections |
dugite_n2c_connections_active | Currently active N2C connections |
dugite_diffusion_mode | 0 = InitiatorAndResponder, 1 = InitiatorOnly |
dugite_peer_sharing_enabled | Whether peer sharing is active (0 or 1) |
dugite_blockfetch_active_peers | Peers currently serving BlockFetch |
dugite_gdd_disconnects_total | Genesis Density Disconnector peer disconnects |
dugite_csj_dynamos / dugite_csj_objectors / dugite_csj_jumpers / dugite_csj_disengaged | ChainSync Jumping peer roles |
dugite_peer_rtt_avg_ms / _min_ms / _max_ms / _samples | EWMA peer RTT summary |
dugite_peer_rtt_band_0_50 / _50_100 / _100_200 / _200_plus | Connected peers bucketed by EWMA RTT |
dugite_peer_governor_target{name="..."} | Peer governor target counts by name |
Mempool and transactions
| Metric | Description |
|---|---|
dugite_mempool_tx_count | Number of transactions in the mempool |
dugite_mempool_tx_max | Maximum transaction capacity of the mempool |
dugite_mempool_bytes | Size of the mempool in bytes |
dugite_n2c_txs_submitted_total / _accepted_total / _rejected_total | N2C local tx-submission outcomes |
Ledger and governance
| Metric | Description |
|---|---|
dugite_delegation_count | Number of active stake delegations |
dugite_vote_delegation_count | Number of vote delegations |
dugite_treasury_lovelace | Total lovelace in the treasury |
dugite_reserves_lovelace | Total lovelace remaining in the reserves pot |
dugite_drep_count | Registered DReps (active + inactive) |
dugite_drep_active | DReps still within their activity window |
dugite_proposal_count | Number of active governance proposals |
dugite_pool_count | Number of registered stake pools |
dugite_committee_total_count / _hot_count / _resigned_count | Constitutional Committee membership |
dugite_committee_threshold_bps | CC threshold in basis points |
dugite_committee_no_confidence | 1 when the committee is in a no-confidence state |
dugite_constitution_present | 1 when a constitution is set |
dugite_gov_dormant_epochs | Consecutive epochs with no governance activity |
dugite_pparam_drep_deposit_lovelace, dugite_pparam_drep_activity_epochs, dugite_pparam_gov_action_deposit_lovelace, dugite_pparam_gov_action_lifetime_epochs, dugite_pparam_committee_min_size, dugite_pparam_committee_max_term_length | Conway governance protocol parameters |
Process and host
| Metric | Description |
|---|---|
dugite_uptime_seconds | Seconds since node startup |
dugite_is_block_producer | 1 when forge credentials are loaded, 0 for relay |
dugite_disk_available_bytes / dugite_disk_used_bytes / dugite_disk_total_bytes | Database volume disk usage |
dugite_mem_resident_bytes | Resident set size (RSS) in bytes |
dugite_mem_peak_bytes | Peak RSS in bytes |
dugite_mem_total_bytes | Total physical memory on the host |
dugite_cpu_percent | Process CPU utilisation as a percentage of one core |
dugite_cpu_seconds_total | Cumulative process CPU time in seconds |
dugite_snapshot_worker_alive | 1 when the background snapshot worker is running |
dugite_utxo_backend_info{backend="..."} | Active UTxO storage backend |
dugite_pool_id_info{pool_id="..."} | Block producer pool identity (block producers only) |
Histograms
| Metric | Buckets (ms) | Description |
|---|---|---|
dugite_peer_handshake_rtt_ms | 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000 | Peer N2N handshake round-trip time |
dugite_peer_block_fetch_range_ms | (same) | BlockFetch range request latency |
Histograms expose _bucket, _count, and _sum suffixes for standard Prometheus histogram queries.
Note: the block-fetch histogram is named
dugite_peer_block_fetch_range_ms, notdugite_peer_block_fetch_ms. The bundled Grafana dashboard still queries the old name — see the caveat under Grafana Dashboard.
Prometheus Configuration
Add the Dugite node as a scrape target in your prometheus.yml:
scrape_configs:
- job_name: 'dugite'
scrape_interval: 15s
static_configs:
- targets: ['localhost:12800'] # mainnet; see the port table above
labels:
network: 'mainnet'
node: 'relay-1'
A ready-made config is committed at config/monitoring/prometheus.yml. It scrapes every well-known dugite port so the dashboard works regardless of which just run-{bp,relay} <network> recipe is active — down targets simply show as DOWN in /targets:
| Port | Role |
|---|---|
| 12796 | preview relay (also the built-in default) |
| 12797 | preview BP |
| 12798 | cardano-node default |
| 12799 | preprod |
| 12800 | mainnet |
Alert rules live alongside it in config/monitoring/prometheus-alerts.yml, covering forge failures, rollback rate, tip age, hot-peer starvation, and RSS growth.
Grafana Dashboard
Dugite ships with a pre-built Grafana dashboard at config/monitoring/grafana-dashboard.json. The dashboard covers all node metrics organized into nine sections:
- Overview — Sync progress gauge, block height, epoch, slot, connected peers, blocks forged
- Node Health — Uptime, disk available (stat + time series)
- Sync & Throughput — Sync progress over time, block apply/receive rate (blk/s), block height, rollbacks
- Peers — Connected peer count over time, peer state breakdown (hot/warm/cold stacked)
- Mempool & Transactions — Mempool tx count, mempool size (bytes), transaction rate (received/validated/rejected)
- Ledger State — UTxO set size, stake delegations, treasury balance (ADA), registered stake pools
- Governance — Registered DReps, active governance proposals
- Block Production — Total blocks forged, block forge rate (blk/h)
- Network Latency — Handshake RTT and block fetch latency percentiles (p50/p95/p99), request counts
- Validation Errors — Error breakdown by type (stacked bars), error totals (bar chart)
Known stale panel: the block-fetch latency panels query
dugite_peer_block_fetch_ms_bucket, but the node emitsdugite_peer_block_fetch_range_ms_bucket. Those panels render empty until the dashboard JSON is updated. Every other metric referenced by the dashboard matches a metric the node actually emits.
Quick Start (Docker)
The fastest way to start a local monitoring stack is with the included script:
# Start Prometheus + Grafana
just monitor-start # or: ./scripts/monitoring/start.sh
# Open the dashboard (admin/admin)
open http://localhost:3000/d/dugite-node/dugite-node
# Check status
just monitor-status # or: ./scripts/monitoring/start.sh status
# Stop
just monitor-stop # or: ./scripts/monitoring/start.sh stop
The script starts Prometheus (port 9090) and Grafana (port 3000) as Docker containers, auto-configures the Prometheus datasource, and imports the Dugite dashboard. Prometheus data is persisted in .monitoring-data/ so metrics survive restarts.
Environment variables for port customization:
| Variable | Default | Description |
|---|---|---|
PROMETHEUS_PORT | 9090 | Prometheus web UI port |
GRAFANA_PORT | 3000 | Grafana web UI port |
DUGITE_METRICS_PORT | 12798 | Port where Dugite exposes metrics |
Importing the Dashboard
- Open Grafana and go to Dashboards > Import
- Click Upload JSON file and select
config/monitoring/grafana-dashboard.json - Select your Prometheus data source when prompted
- Click Import
The dashboard includes an instance template variable so you can monitor multiple Dugite nodes (relays + block producer) from a single dashboard. It auto-refreshes every 30 seconds.
Provisioning
To auto-provision the dashboard, copy it into your Grafana provisioning directory:
cp config/monitoring/grafana-dashboard.json /etc/grafana/provisioning/dashboards/dugite.json
Add a dashboard provider in /etc/grafana/provisioning/dashboards/dugite.yaml:
apiVersion: 1
providers:
- name: Dugite
folder: Cardano
type: file
options:
path: /etc/grafana/provisioning/dashboards
foldersFromFilesStructure: false
Quick Start (macOS)
To quickly preview the dashboard locally with Homebrew:
# Install Prometheus and Grafana
brew install prometheus grafana
# Configure Prometheus to scrape Dugite
cat > /opt/homebrew/etc/prometheus.yml << 'EOF'
global:
scrape_interval: 5s
scrape_configs:
- job_name: dugite
static_configs:
- targets: ['localhost:12796'] # match your config's MetricsPort
EOF
# Provision the datasource
cat > "$(brew --prefix)/opt/grafana/share/grafana/conf/provisioning/datasources/dugite.yaml" << 'EOF'
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
isDefault: true
uid: DS_PROMETHEUS
EOF
# Provision the dashboard
cat > "$(brew --prefix)/opt/grafana/share/grafana/conf/provisioning/dashboards/dugite.yaml" << 'EOF'
apiVersion: 1
providers:
- name: Dugite
folder: Cardano
type: file
options:
path: /opt/homebrew/var/lib/grafana/dashboards
EOF
mkdir -p /opt/homebrew/var/lib/grafana/dashboards
sed 's/${DS_PROMETHEUS}/DS_PROMETHEUS/g' config/monitoring/grafana-dashboard.json \
> /opt/homebrew/var/lib/grafana/dashboards/dugite.json
# Start services
brew services start prometheus
brew services start grafana
# Open the dashboard (default login: admin/admin)
open "http://localhost:3000/d/dugite-node/dugite-node"
To stop:
brew services stop prometheus grafana
Key Queries
| Panel | PromQL |
|---|---|
| Sync progress | dugite_sync_progress_percent / 100 |
| Block throughput | rate(dugite_blocks_applied_total[5m]) |
| Transaction rejection rate | rate(dugite_transactions_rejected_total[5m]) |
| Treasury balance (ADA) | dugite_treasury_lovelace / 1e6 |
| Block forge rate (per hour) | rate(dugite_blocks_forged_total[1h]) * 3600 |
| Handshake RTT p95 | histogram_quantile(0.95, rate(dugite_peer_handshake_rtt_ms_bucket[5m])) |
| Block fetch latency p95 | histogram_quantile(0.95, rate(dugite_peer_block_fetch_range_ms_bucket[5m])) |
| Validation errors by type | rate(dugite_validation_errors_total[5m]) |
| Protocol errors by type | rate(dugite_protocol_errors_total[5m]) |
| Leader election rate | rate(dugite_leader_checks_total[5m]) |
| Active N2N connections | dugite_n2n_connections_active |
| Disk available | dugite_disk_available_bytes |
Console Logging
In addition to the Prometheus endpoint, Dugite logs sync progress to the console every 5 seconds — but only while catching up. Once the node is following the tip (remaining would be 0) the line stops being emitted, so silence here means "synced", not "stalled". The fields are:
| Field | Meaning |
|---|---|
progress | Sync percentage |
epoch | Current epoch number |
block | Current block number |
tip | Best known tip block number |
remaining | Blocks left to apply |
speed | Blocks-per-second throughput |
utxos | UTxO set size |
Example log line:
2026-03-12T12:34:56.789Z INFO dugite_node::node: Syncing progress="95.42%" epoch=512 block=11283746 tip=11300000 remaining=16254 speed="312 blk/s" utxos=15234892
Log output can be directed to stdout, file, or systemd journal. See Logging for full details on output targets, file rotation, and log level configuration.
UTxO RPC (gRPC) Server
Dugite-node ships a native UTxO RPC gRPC server —
the emerging standard programmable interface for UTxO chains adopted by
Dolos, Demeter, and most Cardano indexers. The server is disabled by
default; opt in via the Rpc config block or CLI flags below.
Quick start
# Enable on the default port (50051), bind loopback only.
dugite-node run \
--config config/mainnet/config.json \
--topology config/mainnet/topology.json \
--database-path ./db-mainnet \
--socket-path ./node.sock \
--host-addr 0.0.0.0 --port 3001 \
--rpc-port 50051
# Verify it's up and list registered services.
grpcurl -plaintext localhost:50051 list
# Expect: utxorpc.v1alpha.{sync,query,submit,watch}.{Sync,Query,Submit,Watch}Service
# utxorpc.v1beta.{sync,query,submit,watch}.{Sync,Query,Submit,Watch}Service
# grpc.reflection.v1.ServerReflection
grpcurl -plaintext localhost:50051 utxorpc.v1beta.sync.SyncService/ReadTip
# {
# "tip": {
# "slot": "...",
# "hash": "...",
# "height": "..."
# }
# }
Services exposed
Every service ships in both v1alpha (for backwards compatibility
with older clients) and v1beta (current), with one exception:
QueryService.ReadState exists only in v1beta — upstream added it
after v1alpha was frozen. The spec is pinned in-tree at
crates/dugite-rpc/proto/VERSION (currently v0.19.2).
| Service | Method | Status |
|---|---|---|
SyncService | ReadTip | ✅ implemented |
SyncService | FetchBlock | ✅ implemented |
SyncService | DumpHistory | ✅ implemented |
SyncService | FollowTip (stream) | ✅ implemented |
QueryService | ReadParams | ✅ implemented |
QueryService | ReadUtxos | ✅ implemented |
QueryService | ReadGenesis | ✅ implemented (Shelley-genesis section — see Limitations) |
QueryService | ReadEraSummary | ✅ implemented (real per-era start/end boundaries) |
QueryService | SearchUtxos | ✅ implemented (exact_address / payment_part / delegation_part / asset plus not / all_of / any_of composites) |
QueryService | ReadData | ✅ implemented (bounded scan: live inline datums + mempool tx witness sets) |
QueryService | ReadTx | ✅ implemented (bounded scan: mempool + last ~43 200 slots of VolatileDB) |
QueryService | ReadState | ✅ implemented, v1beta only (minimum-viable envelope: epoch + tip slot) |
SubmitService | SubmitTx | ✅ implemented |
SubmitService | ReadMempool | ✅ implemented |
SubmitService | WaitForTx (stream) | ✅ implemented |
SubmitService | WatchMempool (stream) | ✅ implemented (full TxPredicate filtering, same matcher as WatchTx) |
SubmitService | EvalTx | ✅ implemented (per-redeemer ex_units + Plutus traces) |
WatchService | WatchTx (stream) | ✅ implemented (full TxPredicate filtering: address / asset / mint / not / all_of / any_of; chain-sourced with apply / undo / idle) — see Limitations |
Every method above honours a request's google.protobuf.FieldMask (issue
#1004): unselected fields are pruned from the response, recursively,
including into repeated fields like FetchBlockResponse.block — see
crates/dugite-rpc/src/masking.rs for the exact semantics (a mask that's
absent or empty returns everything, matching the canonical FieldMask doc).
Configuration
JSON config block
Add an Rpc block to config/<network>/config.json:
{
"Rpc": {
"Enabled": true,
"ListenAddr": "127.0.0.1",
"Port": 50051,
"MaxConcurrentStreams": 64,
"StreamBufferSize": 256,
"ReflectionEnabled": true,
"WebEnabled": false,
"AlphaEnabled": true,
"Tls": {
"CertPath": "/etc/dugite/tls/rpc.crt",
"KeyPath": "/etc/dugite/tls/rpc.key"
}
}
}
All fields are optional. Defaults:
| Field | Default | Notes |
|---|---|---|
Enabled | false | Server stays disabled unless this is true or a --rpc-* CLI flag is passed. |
ListenAddr | 127.0.0.1 | Loopback only — protects an unauthenticated TCP gRPC endpoint from the network. Set to 0.0.0.0 only if you've fronted it with TLS or a reverse proxy. |
Port | 50051 | The de-facto UTxO RPC port used by Dolos, Demeter, and others. |
MaxConcurrentStreams | 64 | HTTP/2 streams per connection. |
StreamBufferSize | 256 | Per-stream event buffer. Slow consumers exceeding this drop with RESOURCE_EXHAUSTED. |
ReflectionEnabled | true | Exposes grpc.reflection.v1.ServerReflection so grpcurl -plaintext :50051 list works without a schema bundle. |
WebEnabled | false | Accept gRPC-Web (HTTP/1.1) for browser dApps. Costs a small per-connection bookkeeping when enabled. |
AlphaEnabled | true | Expose v1alpha services alongside v1beta. Operators can pre-disable to test that their clients have migrated. |
Tls | absent | Optional TLS termination. PEM-encoded cert/key on disk; no hot-reload (config changes require a restart). |
CLI flags
CLI flags override the JSON config block:
| Flag | Behaviour |
|---|---|
--rpc-port <PORT> | Force-enable RPC on this port (overrides Rpc.Port). |
--rpc-host <IP> | Force-enable RPC on this address (overrides Rpc.ListenAddr). |
--no-rpc | Force-disable RPC, regardless of the config block. |
Precedence (highest first):
--no-rpc→ server disabled.--rpc-host/--rpc-port→ server enabled with CLI values overriding the config block.Rpc.Enabled = truein JSON → server enabled with config values.- Otherwise → server disabled.
Configuration editor (dugite-config)
The Rpc section is exposed read-only as a JSON Object in
dugite-config so operators can see what's
configured at a glance. Edit sub-fields directly in the config JSON.
TLS
For non-loopback deployments, set Tls.CertPath + Tls.KeyPath to
PEM-encoded files. Both files are read at startup; missing or
unreadable files fail-fast with an io::Error rather than letting
the server come up unsecured.
For mTLS, mutual auth, or rotating certificates, terminate TLS at a
reverse proxy (Envoy, nginx) and leave Dugite's Tls block absent.
Operations
Metrics
The RPC server currently emits no Prometheus metrics. dugite-rpc
defines an RpcMetricsSink trait (request_started,
request_completed, stream_started, stream_ended) so a host can plug
in Prometheus or OpenTelemetry, but dugite-node wires
dugite_rpc::noop_metrics() — every callback is a no-op. No
dugite_rpc_* series appear on the node's /metrics endpoint.
Until a real sink is wired, observe the RPC server through logs (below)
and through the node-level metrics it drives indirectly
(dugite_mempool_tx_count, dugite_n2c_txs_*, and friends).
Logging
Service-level events log at INFO or DEBUG under the
dugite_rpc::server target. Streaming RPCs log slow-consumer drops at
WARN with service / method labels.
Spec-bump workflow
The UTxO RPC .proto files are vendored at
crates/dugite-rpc/proto/utxorpc/ and pinned via
crates/dugite-rpc/proto/VERSION. To refresh:
just bump-utxorpc-spec v0.20.0 # replace with the desired tag
The script:
- Clones the tag from
https://github.com/utxorpc/specinto a tempdir. - Re-copies the Cardano-only subset (
cardano + sync + query + submit + watchfor bothv1alphaandv1beta;bitcoinandhandshakeintentionally omitted). - Rewrites
VERSIONwith the new tag + resolved commit + today's date. - Builds and tests
dugite-rpcso codegen breakage / golden-test drift surfaces before the resulting commit is pushed.
Bumps land as their own PRs alongside any code changes needed to track
upstream protobuf shape changes. The single-source-of-truth lives in
VERSION — out-of-sync bumps (e.g. files refreshed without VERSION
updated, or vice versa) are caught by code review against the diff.
Limitations
ReadGenesispopulates the full Shelley-genesis section ofcardano.Genesis(network_magic, network_id, system_start, security_param, epoch_length, slot_length, max_lovelace_supply, max_kes_evolutions, slots_per_kes_period, update_quorum, active_slots_coeff — 10 of the message's 34 fields). Byron (avvm_distr,boot_stakeholders,heavy_delegation,vss_certs, ...), Alonzo (cost_models,execution_prices, ...), and Conway (committee,constitution,drep_voting_thresholds, ...) sections are unset, as are Shelley'sgen_delegs/initial_funds/staking— those genesis structs aren't retained pastdugite-nodestartup today. Tracked as #1009.ReadEraSummary's per-eraprotocol_paramsis unset: dugite's ledger retains only the CURRENT era'sPParams, not a per-era history, so there is nothing truthful to report for a past era.start/endboundaries ARE real (slot, epoch, and absolute wall-clock ms).SearchUtxoswith a fully-wildcard predicate (nomatch/ combinators) is rejected withUNIMPLEMENTED: dugite refuses to materialise the entire UTxO set in a single response. Supply at least one selector (address / payment_part / delegation_part / asset / composite) so the result set is bounded.ReadTxwalks at most the last ~43 200 slots ofVolatileDB. A chain-wide tx index would extend the lookup window to immutable history; not built today.ReadDatascans the live UTxO set's inline datums and the mempool's witness-set datums. Witness-set datums from immutable blocks are not retained — clients that need them should consult the originating tx viaReadTx.ReadState'sAnyChainStateData.cardanois currently empty: the endpoint returns the ledger tip + epoch only. Per-query state projections (stake-pool distribution, DRep info) land on top of this stub.EvalTx's per-redeemerex_unitsare CEK-machine consumed values, not declared. Cost-model overrides are not yet read from protocol params — the CEK falls back to per-step defaults, which is conservative (over-approximates) and therefore safe for fee-estimation use cases but may diverge slightly from cardano-node on the high end.WatchTx/WatchMempoolfilter on tx output fields (produces/has_address/moves_asset) and minting (mints_asset). TwoTxPatternleaves are not implemented:consumes(needs resolved-input UTxO data the watch paths don't have) andhas_certificate(needs a certificate-type matcher not yet built). A request naming either — anywhere, including nested undernot/all_of/any_of— is rejected withUNIMPLEMENTEDbefore it ever subscribes, rather than silently accepted and under-filtered.WatchTxis chain-sourced, matching the proto's own "stream transactions from the chain" comment (SubmitService.WatchMempoolis the pre-confirmation counterpart). It subscribes to the sameTipFeed/TipRollbackbroadcastFollowTipuses: each applied block yields oneapplyWatchTxResponseper matching tx (withAnyChainTx.blockpopulated) or a singleidleif the block matched nothing, and a rolled-back block replays its cached matches asundo, most-recent-first. The replay data comes from a per-subscriber bounded history (HISTORY_CAP= 4,320 blocks, above mainnetk= 2,160) built from the subscriber's own apply-time observations —TipRollbackdoes not need to carry the rolled-back block's contents. Was #1007.FollowTipapply events carryAnyChainBlock.native_bytes(the raw block CBOR); clients that only need tip metadata can ignore the payload.
See also
- UTxO RPC spec
crates/dugite-rpc/proto/VERSION— the pinned spec tag.crates/dugite-rpc/tests/— golden + integration tests covering each service.
Relay Node
A relay node is the public-facing component of a stake pool deployment. It bridges your block producer to the wider Cardano network while shielding the BP from direct internet exposure.
Role in Stake Pool Architecture
In a properly secured stake pool, the block producer never communicates directly with the public network. Instead, one or more relay nodes handle all external connectivity:
graph LR
Internet["Cardano Network"] <-->|N2N| Relay1["Relay 1<br/>Public IP"]
Internet <-->|N2N| Relay2["Relay 2<br/>Public IP"]
Relay1 <-->|Private| BP["Block Producer<br/>Private IP"]
Relay2 <-->|Private| BP
- Relays accept inbound connections from any Cardano peer, discover peers via bootstrap/ledger, and forward blocks to/from the BP.
- Block producer connects only to your relays, never to the public internet.
Running a Relay
A relay is simply a Dugite node started without block production keys:
dugite-node run \
--config config.json \
--topology topology-relay.json \
--database-path ./db \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001
Tip: For initial sync, use Mithril snapshot import first to skip millions of blocks.
Relay Topology
A relay topology combines public peer discovery with a local root pointing to your block producer.
Preview Testnet Relay
{
"bootstrapPeers": [
{ "address": "preview-node.play.dev.cardano.org", "port": 3001 }
],
"localRoots": [
{
"accessPoints": [
{ "address": "10.0.0.10", "port": 3001 }
],
"advertise": false,
"hotValency": 1,
"warmValency": 2,
"trustable": true,
"behindFirewall": true
}
],
"publicRoots": [
{ "accessPoints": [], "advertise": false }
],
"useLedgerAfterSlot": 102729600
}
Mainnet Relay
{
"bootstrapPeers": [
{ "address": "backbone.cardano.iog.io", "port": 3001 },
{ "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 },
{ "address": "backbone.mainnet.emurgornd.com", "port": 3001 }
],
"localRoots": [
{
"accessPoints": [
{ "address": "10.0.0.10", "port": 3001 }
],
"advertise": false,
"hotValency": 1,
"warmValency": 2,
"trustable": true,
"behindFirewall": true
}
],
"publicRoots": [
{ "accessPoints": [], "advertise": false }
],
"useLedgerAfterSlot": 177724800
}
Key topology settings for relays:
bootstrapPeers— Trusted initial peers for syncing from genesis or after restart.localRootswithbehindFirewall: true— Your block producer. The relay waits for inbound connections from the BP rather than connecting outbound, which works correctly when the BP is behind a firewall.useLedgerAfterSlot— Enables ledger-based peer discovery once synced past this slot, providing decentralized peer resolution from on-chain stake pool registrations.advertise: false— Set totrueif you want your relay to be discoverable via peer sharing.
Multiple Relays
Running two or more relays provides redundancy. If one relay goes down, the block producer stays connected through the other.
To run multiple relays on the same machine, use different ports, database paths, and socket paths:
# Relay 1 on port 3001
dugite-node run \
--config config.json \
--topology topology-relay1.json \
--database-path ./db-relay1 \
--socket-path ./relay1.sock \
--host-addr 0.0.0.0 \
--port 3001
# Relay 2 on port 3002
dugite-node run \
--config config.json \
--topology topology-relay2.json \
--database-path ./db-relay2 \
--socket-path ./relay2.sock \
--host-addr 0.0.0.0 \
--port 3002
Each relay's topology should include the block producer as a local root. The block producer's topology should list all relays (see Block Producer Topology).
For production deployments, run relays on separate machines or in different availability zones for better fault tolerance.
Firewall Configuration
Relay nodes need port 3001 (or your chosen port) open to the public for Cardano N2N traffic. The block producer should only be reachable from your relays.
Relay firewall rules
# Allow inbound Cardano N2N from anywhere
sudo ufw allow 3001/tcp
# Allow SSH (adjust as needed)
sudo ufw allow 22/tcp
sudo ufw enable
Block producer firewall rules
# Allow inbound only from relay IPs
sudo ufw allow from <relay1-ip> to any port 3001
sudo ufw allow from <relay2-ip> to any port 3001
# Allow SSH (adjust as needed)
sudo ufw allow 22/tcp
# Deny everything else
sudo ufw default deny incoming
sudo ufw enable
Important: The block producer should have no public-facing ports. All Cardano traffic flows exclusively through your relays.
Monitoring
Dugite exposes Prometheus metrics on the port pinned by your config's MetricsPort field — 12796 for preview, 12799 for preprod, 12800 for mainnet in the shipped configs. The built-in fallback when the field is absent is 12796. Key metrics to watch on a relay:
| Metric | What it tells you |
|---|---|
dugite_peers_connected | Number of active peer connections. Should be > 0 at all times |
dugite_sync_progress_percent | Sync progress (10000 = 100%). Must be at 100% for the BP to produce blocks |
dugite_blocks_received_total | Total blocks received from peers. Should increase steadily |
dugite_slot_number | Current slot. Compare against network tip to verify sync |
dugite_tip_age_seconds | Seconds since the tip slot time. Sustained growth means the relay is falling behind |
curl -s http://localhost:12800/metrics | grep -E "peers_connected|sync_progress"
Also worth wiring into your supervisor: /ready returns 503 until sync progress reaches 99.9%, and /live returns 503 when no block has been applied within --liveness-threshold-secs (default 600).
See Monitoring for the full list of available metrics and Grafana dashboard setup.
Next Steps
- Block Producer — Set up key generation, operational certificates, and block production
- Topology — Full topology format reference
- Monitoring — Prometheus metrics and alerting
Block Producer
Dugite can operate as a block-producing node (stake pool). This requires KES keys, VRF keys, and an operational certificate.
Architecture
A block producer is never directly exposed to the public internet. Instead, it sits behind one or more relay nodes that handle all external network connectivity. The relays forward blocks and transactions to the BP over a private network, and the BP announces forged blocks back through the relays.
Status: Dugite block forging is operational and on-chain verified. Block 4265661 at slot 111661041 was forged by Dugite, accepted by the network, and confirmed on the canonical chain (Conway era, 1 tx, built upon by a subsequent block). Ongoing soak testing runs via Sandstone Pool [SAND] on both public testnets — preview (pool ID
6954ec11cf7097a693721104139b96c54e7f3e2a8f9e7577630f7856) and preprod. Forged blocks are additionally cross-validated byte-for-byte against cardano-node on the local devnet before every release.
See the Complete Deployment section at the bottom of this page for the full architecture diagram and setup checklist.
Overview
A block producer is a node that has been registered as a stake pool and is capable of minting new blocks when it is elected as a slot leader. The block production pipeline involves:
- Slot leader check — Each slot, the node uses its VRF key and the epoch nonce to determine if it is elected to produce a block.
- Block forging — If elected, the node assembles a block from pending mempool transactions, signs it with the KES key, and includes the VRF proof.
- Block announcement — The forged block is propagated to connected peers via the N2N protocol.
Required Keys
Cold Keys (Offline)
Cold keys identify the stake pool and should be kept offline (air-gapped) after initial setup.
Generate cold keys using the CLI:
dugite-cli node key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter
--operational-certificate-issue-counter-fileis the cardano-cli canonical spelling. The bare--operational-certificate-issue-counterand the legacy dugite spelling--operational-certificate-counter-fileare both still accepted as aliases.
KES Keys (Hot)
KES (Key Evolving Signature) keys are rotated periodically. Each KES key is valid for maxKESEvolutions periods of slotsPerKESPeriod slots each. On mainnet, preprod, and preview alike this is 62 periods of 129,600 slots — about 93 days.
Generate KES keys:
dugite-cli node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
VRF Keys
VRF (Verifiable Random Function) keys are used for slot leader election. They are generated once and do not need rotation.
Generate VRF keys:
dugite-cli node key-gen-VRF \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
key-gen-KES,key-gen-VRF, andkey-hash-VRFuse cardano-cli's canonical mixed-case spelling. All-lowercase forms are accepted as aliases.
Operational Certificate
The operational certificate binds the cold key to the current KES key. It must be regenerated each time the KES key is rotated.
Issue an operational certificate:
dugite-cli node issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter \
--kes-period <current-kes-period> \
--out-file opcert.cert
The --kes-period should be set to the current KES period at the time of issuance. You can calculate the current KES period as:
current_kes_period = current_slot / slots_per_kes_period
slotsPerKESPeriod is 129,600 on mainnet, preprod, and preview.
Rather than computing it by hand, ask a running node:
dugite-cli query kes-period-info \
--op-cert-file opcert.cert \
--socket-path ./node.sock \
--testnet-magic 1
This reads the opcert, decodes its counter and issue period, queries the node for the current KES period (GetCurrentKESPeriod), and reports whether the certificate is valid, expired, or not yet valid — matching cardano-cli query kes-period-info.
Running as Block Producer
Pass the key and certificate paths when starting the node:
dugite-node run \
--config config.json \
--topology topology.json \
--database-path ./db \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001 \
--shelley-kes-key kes.skey \
--shelley-vrf-key vrf.skey \
--shelley-operational-certificate opcert.cert
When all three arguments are provided, the node enters block production mode. Without them, it operates as a relay-only node.
An optional fourth flag, --shelley-cold-key <FILE>, points at the cold signing key and is used only to derive the pool ID for logging and the dugite_pool_id_info metric. It is not required for forging — and on a properly secured setup the cold key lives on an air-gapped machine, so most operators leave it unset.
Block Producer Topology
A block producer should not be directly exposed to the public internet. Instead, it should connect only to your relay nodes:
{
"bootstrapPeers": null,
"localRoots": [
{
"accessPoints": [
{ "address": "relay1.example.com", "port": 3001 },
{ "address": "relay2.example.com", "port": 3001 }
],
"advertise": false,
"hotValency": 2,
"warmValency": 3,
"trustable": true
}
],
"publicRoots": [{ "accessPoints": [], "advertise": false }],
"useLedgerAfterSlot": -1
}
Key points:
- No bootstrap peers — The block producer syncs exclusively through your relays.
- No public roots — No connections to unknown peers.
- Ledger peers disabled —
useLedgerAfterSlot: -1disables ledger-based peer discovery. - Only local roots — All connections are to your own relay nodes.
Leader Schedule
You can compute your pool's leader schedule against a running node:
dugite-cli query leadership-schedule \
--socket-path ./node.sock \
--testnet-magic 1 \
--genesis config/preprod/shelley-genesis.json \
--vrf-signing-key-file vrf.skey \
--stake-pool-id <pool-id-hex> \
--current
Required: --genesis (Shelley genesis path) and --vrf-signing-key-file. Identify the pool with either --stake-pool-id or --cold-verification-key-file. Choose the epoch with --current or --next. Output defaults to JSON; use --output-text for the human-readable table, and --out-file to write to disk.
This outputs all slots where your pool is elected to produce a block in the given epoch. The nonce, epoch boundaries, and stake distribution are read from the node — they are not passed as flags.
KES Key Rotation
KES keys must be rotated before they expire. A key issued at period N is valid for periods N through N + 61 (maxKESEvolutions = 62). At 129,600 slots per period and 1-second slots, that is roughly 93 days.
When to rotate
Check where you stand at any time:
dugite-cli query kes-period-info \
--op-cert-file opcert.cert \
--socket-path ./node.sock \
--testnet-magic 1
Plan the rotation about two weeks before the expiry period. Missing it is not a soft failure: once the current KES period passes startPeriod + 62, the node can no longer sign headers and the pool silently stops producing blocks.
Rotation procedure
-
Generate new KES keys:
dugite-cli node key-gen-KES \ --verification-key-file kes-new.vkey \ --signing-key-file kes-new.skey -
Issue a new operational certificate with the new KES key (on the air-gapped machine, where
cold.skeylives):dugite-cli node issue-op-cert \ --kes-verification-key-file kes-new.vkey \ --cold-signing-key-file cold.skey \ --operational-certificate-issue-counter-file opcert.counter \ --kes-period <current-kes-period> \ --out-file opcert-new.certissue-op-certincrements the counter file in place. Carry the updatedopcert.counterback to the air-gapped store — reusing a stale counter produces a certificate the network rejects. -
Copy
kes-new.skeyandopcert-new.certto the block producer, replace the live files, and restart:cp kes-new.skey kes.skey cp opcert-new.cert opcert.cert # Restart the node (SIGTERM, never SIGKILL — see the warning below) -
Confirm the new cert took effect:
dugite-cli query kes-period-info --op-cert-file opcert.cert --socket-path ./node.sock --testnet-magic 1The node also logs the operational certificate sequence number and KES period at startup.
Practicalities
- Rotation requires a restart. There is no hot-reload path for forging credentials. Schedule it in a slot where you are not expected to be leader, and keep the restart short — a Mithril-seeded node rejoins in well under a minute, but a cold replay does not.
- Stop with
SIGTERM, neverSIGKILL. A hard kill can leave the ImmutableDB's active-chunk index unflushed. The node reconciles this at open, but a clean shutdown avoids the recovery path entirely. - The counter is the state that matters. KES keys are cheap to regenerate; a desynchronised issue counter is what actually locks you out. Back up
opcert.counteralongside the cold key. - Rotate the KES key, not the VRF key. VRF keys are permanent — regenerating one changes your leader schedule and effectively orphans the pool's registration.
Security Recommendations
- Keep cold keys on an air-gapped machine. They are only needed to issue new operational certificates.
- Restrict access to the block producer machine. Only your relay nodes should be able to connect.
- Monitor your pool's block production. Use the Prometheus metrics endpoint to track
dugite_blocks_forged_total(blocks this node minted) alongsidedugite_leader_checks_totalanddugite_forge_failures_total.dugite_blocks_applied_totalcounts every block applied from any source, so it tells you nothing about your own forging. - Set up KES key rotation reminders well before expiry (2 weeks in advance is a good practice).
- Use firewalls to ensure the block producer is not reachable from the public internet.
Snapshot Recovery & Block Forging Readiness
When a block producer starts up, several subsystems must be initialized before it can begin forging blocks. The path to readiness depends on how the node was bootstrapped.
Epoch Nonce
The epoch nonce is critical for VRF leader election. It is serialized in the ledger snapshot alongside the consensus state (epoch_nonce, evolving_nonce, candidate_nonce, last_epoch_block_nonce), so it is immediately authoritative after a snapshot load or Mithril import — matching the Haskell cardano-node's treatment of praosStateEpochNonce. Forging is enabled as soon as the node catches up to the chain tip and its pool has non-zero stake in the "set" snapshot; no additional epoch boundary is required.
Pool Stake Reconstruction
On startup, after loading a ledger snapshot, the node rebuilds the stake distribution from the UTxO store to ensure consistency:
rebuild_stake_distribution()recomputes per-pool stake totals from the current UTxO set and delegation map.recompute_snapshot_pool_stakes()updates the mark/set/go snapshots so that the "set" snapshot (used for leader election) reflects the rebuilt distribution.
This runs automatically when the UTxO store is non-empty. After completion, the node logs the pool's stake in the "set" snapshot:
Block producer: pool stake in 'set' snapshot (used for leader election)
pool_id=<hash>, pool_stake_lovelace=<n>, total_active_stake_lovelace=<n>, relative_stake=<f>
If your pool shows zero stake after startup, verify:
- The pool registration certificate transaction is confirmed on-chain.
- At least one stake address is delegated to the pool and that delegation is confirmed.
- The UTxO store was properly attached (the node logs
Rebuilding stake distribution from UTxO storeon startup). - The "set" snapshot epoch is recent enough to include your pool's registration and delegation.
Epoch Numbering
Each network has its own epoch length defined in the Shelley genesis configuration:
| Network | epoch_length | Approximate Duration |
|---|---|---|
| Mainnet | 432,000 | 5 days |
| Preview | 86,400 | 1 day |
| Preprod | 432,000 | 5 days |
When a ledger snapshot is loaded, the node recalculates the current epoch from the tip slot using the genesis parameters. If the snapshot was saved with incorrect epoch parameters (for example, using mainnet's default 432,000 instead of preview's 86,400), the epoch number baked into the snapshot will be wrong. The node detects this automatically and corrects it:
Snapshot epoch differs from computed epoch — correcting
snapshot_epoch=<wrong>, correct_epoch=<right>, tip_slot=<slot>
Without this correction, apply_block would attempt to process hundreds of spurious epoch transitions, and the stake snapshots would land at wrong epochs, causing pool_stake=0 for block producers.
Fork Recovery
When a block producer forges a block but another pool wins the slot battle (their block is adopted by the network instead), the forged block becomes orphaned. Dugite detects this situation during chain synchronization and recovers automatically.
How Fork Detection Works
During ChainSync, the node presents historical chain points (up to 10 ancestors, walked backwards through the volatile DB) to the upstream peer. If the local tip is an orphaned forged block that the peer does not recognize, the ancestor blocks provide fallback intersection points.
Recovery Cases
Case A: Full Reset. The intersection falls back to Origin despite having a non-trivial ledger tip. This means no peer recognizes any of the node's chain points. The node:
- Clears the volatile DB.
- Rolls back the ledger state to Origin.
- Disables strict VRF verification (so replay can proceed without rejecting blocks due to stale nonce).
- Reconnects and replays from the ImmutableDB.
Case B: Targeted ImmutableDB Replay. The intersection is behind the ledger tip but not at Origin. The node:
- Clears the volatile DB.
- Detaches the LSM UTxO store (switches to fast in-memory replay).
- Replays the ImmutableDB from genesis up to the intersection slot.
- Reattaches the UTxO store and resumes syncing from the canonical chain.
In both cases, orphaned forged blocks are not propagated to downstream peers. The node resumes normal operation on the canonical chain after recovery completes.
Troubleshooting Block Producer Issues
"Block producer has ZERO stake"
Block producer has ZERO stake in 'set' snapshot — will not be elected slot leader.
This warning appears at startup when the "set" snapshot contains no stake for your pool. Possible causes:
- Pool not registered: Submit a pool registration certificate transaction and wait for it to be confirmed.
- No delegations: At least one stake address must delegate to the pool. Submit a delegation certificate and wait for confirmation.
- Snapshot too old: The "set" snapshot reflects stake from two epoch boundaries ago. A newly registered pool must wait 2 epoch transitions before appearing in the "set" snapshot.
- UTxO store not attached: If the node started without a UTxO store, stake reconstruction is skipped. Check for the
Rebuilding stake distribution from UTxO storelog message.
"VRF leader eligibility check failed"
VRF leader check failures during the first few epochs after a full replay are non-fatal and expected. The mark/set/go snapshot rotation means the "set" snapshot needs up to 3 epoch transitions to stabilize with correct stake distributions derived from the replayed state. During this window:
- The node may compute incorrect leader eligibility for some slots.
- Your pool may miss some leader slots — this is temporary and self-correcting.
Pool Registered but No Forge Attempts
If your pool is registered on-chain but the node never logs any forge attempts:
- Check the "set" snapshot log: Look for the startup message
Block producer: pool stake in 'set' snapshot. Verify thatpool_stake_lovelaceis greater than zero. - Check the "set" snapshot availability: If you see
Block producer: no 'set' snapshot available — leader election disabled until epoch transition, the node has not yet completed enough epoch transitions. Wait for at least 2 epoch boundaries. - Verify key files: Ensure
--shelley-kes-key,--shelley-vrf-key, and--shelley-operational-certificateare all provided and point to valid files. Without all three, the node runs in relay-only mode. - Check KES period: If the KES key has expired (current KES period exceeds the operational certificate's start period plus
maxKESEvolutions), rotate the KES key and issue a new operational certificate.
macOS App Nap (macOS only)
macOS can suspend background processes via "App Nap" to save power. A suspended node misses every leader slot during the freeze window. Wrap the node in caffeinate to prevent this:
caffeinate -dimsu dugite-node run \
--config config.json \
--topology topology.json \
--database-path ./db \
--socket-path ./node.sock \
--shelley-kes-key kes.skey \
--shelley-vrf-key vrf.skey \
--shelley-operational-certificate opcert.cert
The -dimsu flags prevent disk-idle, display, idle, system, and user-idle sleep from suspending the process. Required for reliable block production on macOS development machines.
Complete Deployment
A full stake pool deployment consists of one block producer and one or more relay nodes working together:
graph TB
subgraph Public Network
Peers["Cardano Peers"]
end
subgraph Your Infrastructure
subgraph Relay Tier
R1["Relay 1<br/>Port 3001<br/>Public IP"]
R2["Relay 2<br/>Port 3001<br/>Public IP"]
end
subgraph Private Network
BP["Block Producer<br/>Port 3001<br/>Private IP<br/>KES + VRF + OpCert"]
end
end
Peers <-->|N2N| R1
Peers <-->|N2N| R2
R1 <-->|Private| BP
R2 <-->|Private| BP
Deployment Checklist
-
Set up relay nodes (Relay Node guide)
- Install Dugite on relay machines
- Import Mithril snapshot for fast initial sync
- Configure relay topology with bootstrap peers and BP as local root
- Open port 3001 to the public
- Start relay nodes and verify they sync to tip
-
Set up the block producer (this page)
- Install Dugite on the BP machine
- Import Mithril snapshot
- Generate cold keys, VRF keys, and KES keys
- Issue an operational certificate
- Configure BP topology with relays as local roots (no public peers)
- Restrict firewall to relay IPs only
- Start the BP node with
--shelley-kes-key,--shelley-vrf-key,--shelley-operational-certificate
-
Register the stake pool on-chain (requires a transaction with pool registration certificate)
-
Verify block production
- Confirm sync progress is 100% on all nodes
- Check
dugite_peers_connectedmetrics on relays and BP - Monitor
dugite_blocks_forged_totalmetric on the BP after epoch transition - Set up monitoring and KES rotation reminders
Local Testnet
A 3-node loopback testnet for verifying dugite block production and diffusion against the Haskell reference implementation. One dugite block producer, one dugite relay, and one cardano-node validator (relay role), all on the same machine, all on the loopback interface — the cardano-node connects through the dugite relay.
What this is
graph LR dbp[dugite-bp<br/>N2N 3001<br/>metrics 12798<br/>pool1 — sole forger] <--> dr[dugite-relay<br/>N2N 3002<br/>metrics 12799<br/>hub] dr <--> cbp[cardano-node<br/>N2N 3003<br/>validator only]
dugite-bp is the sole block producer. cardano-node runs as a passive validator (no forging keys passed) and chainsync+blockfetches every block forged by dugite-bp, applying each one through the Haskell ledger. This gives us byte-exact cross-validation of dugite's forged blocks against the reference implementation with zero risk of asymmetric forks (no two forgers competing for the same height).
The chain boots into Conway PV10 from a fresh genesis with a single
forging pool (pool1, 100% of active stake; pool2's keys exist but its
stake is 0), 1-second slots, activeSlotsCoeff = 0.5, epochLength = 400,
and securityParam = 40. Blocks are minted every ~2 seconds on average
and an epoch elapses every ~6.7 minutes, so the 30-minute soak crosses
~4 epoch boundaries (enough for reward distribution to complete before
the soak ends).
Historical note: prior to 2026-05-18, cardano-node also ran as a forger (pool2, equal stake) for symmetric multi-forger cross-validation. That topology produced an asymmetric-fork class that no per-tx test could survive: the slower side's leader-slot timer fired before propagation completed and each pool ended up on its own short chain permanently (first-seen tiebreaker, equal lengths). Making cardano-node a validator eliminates that class entirely while preserving the cross-validation we actually rely on (does cardano-node accept every dugite block byte-for-byte?).
Prerequisites
cardano-node>= 11.0.1 andcardano-cli>= 11.0.0 on$PATHtarget/release/dugite-nodebuilt (cargo build --releasefrom the repo root)jqfor JSON manipulation- ~2 GB of free disk for the soak
- macOS: the system-builtin
caffeinate(used to suppress App Nap during the soak). This dependency is macOS-only; on Linux the soak runs unwrapped.
The setup script's prereq check will refuse to run if any of these are missing.
Justfile recipes
The scripts below are the underlying entry points. For everyday use the
justfile wraps them — both shapes are equivalent:
| Recipe | Wraps |
|---|---|
just devnet-setup | setup.sh |
just devnet-run | run.sh |
just devnet-soak | soak.sh (30 min) |
just devnet-verify | verify.sh against the most recent evidence round |
just devnet-stop | stop.sh |
just devnet-report [TAG] | single-round release report from the latest evidence |
just devnet-validate-smoke | one boot, ~5 min: setup + run + tx-zoo (01, 02, 08) + log-level predicate + report. PR gate for core crates. |
just devnet-validate-extended | ~75 min, 3 rounds: used for release tagging |
devnet-verify and devnet-report locate the newest directory containing a
metadata.json under evidence/ or evidence-archive/auto/ — that file is
what distinguishes a real soak round from the evidence directories that
09-cli-parity and protocols/ also create.
Both devnet-validate-* recipes install an EXIT trap that runs stop.sh,
so an aborted run does not leave orphaned nodes holding ports 3001-3003.
One-time setup
./testnet/local-devnet/setup.sh
This generates a fresh genesis (4 files: byron, shelley, alonzo, conway),
key sets for two stake pools, four stake delegators, three genesis keys, and
one UTxO funding key. All output lands under testnet/local-devnet/genesis/,
testnet/local-devnet/keys/, and testnet/local-devnet/config/ (rendered
configs). Generated keys and genesis files are gitignored.
Expected output ends with:
[INFO] All configs + topologies rendered to testnet/local-devnet/config/
[INFO] Setup complete. Next: ./run.sh
The genesis start time is set to "now + 30 seconds." Re-run setup.sh if more
than ~5 minutes pass before you call run.sh (the start-time freshness check
will refuse to start a stale chain).
Running the network
./testnet/local-devnet/run.sh
This starts the three nodes in the background (caffeinate-wrapped on macOS),
records PIDs to state/<node>.pid, sends logs to logs/<node>.log, and
exposes N2C sockets under /tmp/ld-$(id -u)/:
| Node | Socket |
|---|---|
dugite-relay | /tmp/ld-<uid>/relay.sock |
dugite-bp | /tmp/ld-<uid>/dbp.sock |
cardano-bp | /tmp/ld-<uid>/cbp.sock |
Sockets live under
/tmp/ld-<uid>/rather than inside the repo because macOS capssun_pathat 104 bytes, and both a worktree path and the default macOS$TMPDIR(/var/folders/...) blow past that.
You can query each socket with cardano-cli (or dugite-cli, which speaks
the same N2C protocol):
cardano-cli query tip \
--testnet-magic 42 \
--socket-path "/tmp/ld-$(id -u)/dbp.sock"
To stop the network:
./testnet/local-devnet/stop.sh
This sends SIGTERM, waits 5 seconds, then SIGKILL if needed. DBs and logs
are preserved in state/ and logs/.
Running the soak test
./testnet/local-devnet/soak.sh # default: 1800s (30 minutes)
./testnet/local-devnet/soak.sh 300 # 5-minute smoke test
The soak runs three concurrent samplers while it's alive:
- tip-sampler — every 5 seconds, queries
tipon each socket and appends(ts, node, slot, block_no, hash, era)rows totip-samples.csv. - block-recorder — tails the three node logs and writes one row per first- sight of each block, with observer, forge/recv flag, slot, hash, and (for forge events) the issuer's vkey.
- tx-injector — at T+2 min, T+10 min, and T+20 min, submits 5 self-transfer payment transactions to each of the 3 sockets (15 per wave, 45 total) and records each submission's txid and return code.
Evidence lands in testnet/local-devnet/evidence/<timestamp>/. A heartbeat
line is printed every 30 seconds with current tips from all three nodes.
Verifying results
After the soak finishes, soak.sh does not automatically run verify — run it
manually:
./testnet/local-devnet/verify.sh testnet/local-devnet/evidence/<timestamp>/
The verifier evaluates five pass/fail predicates and writes
evidence/<timestamp>/report.md. Predicates:
| # | Predicate | Pass condition |
|---|---|---|
| 1 | Block forge cross-check | Every canonical (slot, hash) pair is seen by all three observers in blocks.csv. Orphans are excluded. |
| 2 | Per-BP forge attribution | dugite-bp forged >= 3 blocks; pool2 forged 0 (cardano-node is a validator). Expected ~900 by dugite-bp at f=0.5, σ=1.0 — failure at 3 is a real wiring bug, not a slot-lottery flake. Any forge events attributed to a non-dugite-bp issuer are a setup error. |
| 3 | Transaction inclusion round-trip | Every submitted tx has submit_rc=0 and (when run with the devnet up) appears in all three nodes' UTxO sets at the genesis payment address. SKIPs when the round submitted no txs. |
| 4 | Tip parity over time | At >=95% of 5-second ticks (excluding the warmup window), all three nodes report tips within 2 blocks of each other. |
| 5 | Tip age | dugite_tip_age_seconds stays below threshold on every dugite node after the catch-up grace window. SKIPs when the soak is too short to sample. |
The report.md includes a metadata snapshot (versions, genesis hashes, magic),
counts (block events, tx submissions, tip samples), a forge-attribution
breakdown, and a per-predicate result table.
You can also self-test the verifier (without a real soak) using committed test fixtures:
./testnet/local-devnet/verify.sh --self-test
Topology & port reference
| Process | N2N | Metrics | Socket | Config | Topology |
|---|---|---|---|---|---|
dugite-bp (pool1, sole forger) | 3001 | 12798 | /tmp/ld-<uid>/dbp.sock | dugite-bp.config.json | dugite-bp.topology.json |
dugite-relay (hub) | 3002 | 12799 | /tmp/ld-<uid>/relay.sock | dugite-relay.config.json | dugite-relay.topology.json |
cardano-node (validator) | 3003 | — | /tmp/ld-<uid>/cbp.sock | cardano-bp.config.json | cardano-bp.topology.json |
The devnet uses the standard Cardano N2N port (3001) for dugite-bp and
single-digit increments for the relay (3002) and the Haskell BP (3003). The
metrics ports follow the same convention: dugite-bp keeps the well-known
Prometheus default (12798), the relay exposes 12799. If a public-network
soak is running on the same host it must be stopped before the devnet boots,
since both processes would otherwise bind 3001/12798.
Monitoring with dugite-monitor
Because dugite-bp runs on the default metrics port (12798), the bundled
TUI monitor connects with no overrides. In a separate terminal once the
devnet is up:
./target/release/dugite-monitor
To inspect the relay instead of the BP, point the monitor at its metrics port:
./target/release/dugite-monitor --metrics-url http://localhost:12799/metrics
The cardano-node validator does not expose a Prometheus endpoint in this
devnet (its EKG/Prometheus exporters are disabled to keep the configuration
minimal and avoid port collisions).
Configuration reference
The genesis is generated by cardano-cli conway genesis create-testnet-data
with two override fragments committed under config/spec/. Only fields that
differ from cardano-cli's defaults are listed below.
config/spec/shelley-spec.json:
| field | value | purpose |
|---|---|---|
slotLength | 1.0 | 1-second slot duration |
activeSlotsCoeff | 0.5 | f = 0.5; ~2s expected block time |
epochLength | 400 | ~6.7 minutes per epoch -> ~4 epoch transitions in the 30-min soak. The Praos lower bound is 3k/f = 240 slots; we stay safely above that while keeping reward/governance cycles short enough for tx-zoo. |
securityParam | 40 | small k -> fast immutability (3k/f = 240 slots) |
updateQuorum | 2 | matches the 3 genesis keys (2-of-3) |
maxLovelaceSupply | 60_000_000_000_000_000 | 60 B ADA, mainnet-shaped |
networkMagic | 42 | local devnet magic |
protocolParams.protocolVersion | {major: 10, minor: 0} | boots straight into Conway |
Note that the protocol version lives in shelley-spec.json, not in the
Conway spec. config/spec/conway-spec.json carries the Conway governance
parameters — pool/DRep voting thresholds, govActionLifetime (6 epochs),
govActionDeposit, dRepDeposit, dRepActivity,
minFeeRefScriptCostPerByte, and the Plutus V3 cost model. Those values are
load-bearing for the governance, proposal, and voting categories of the
tx-zoo, so they are not inert filler even on a short run.
Troubleshooting
ERROR: cardano-cli x.y.z < 11.0.0 required— install a newer cardano-cli; see prerequisites.ERROR: Port 3001 is in use— another devnet (or the public soak rig) is using a port. Run./stop.shorlsof -iTCP:3001.Genesis is N seconds old (>300s). Re-run ./setup.sh— the start time has drifted; re-run setup.- Tips not advancing after
run.sh— checklogs/<node>.logfor the failing node. Most common cause is a KES key path mismatch (re-runsetup.sh). - Soak hangs on macOS — confirm
caffeinateis wrapping the dugite processes viaps auxw | grep caffeinate. App Nap can freeze dugite for tens of minutes without it. dugite-monitorshows no data — confirm the BP is exposing metrics on 12798 withcurl -s localhost:12798/metrics | head. If empty, the BP either failed to start or its config doesn't have the Prometheus exporter enabled (the devnet template enables it by default).
What this validates
- Dugite block production end-to-end (forge -> adopt -> diffuse)
- Dugite relay's ChainSync/BlockFetch serving (Rust -> Haskell validator)
- Dugite N2N peer connection lifecycle on loopback
- Dugite N2C local-socket tx submission, query tip, query utxo
- Byte-exact acceptance of every dugite-forged block by cardano-node 11.0.1+
(the Haskell ledger applies each block; any header / body / Conway
predicate mismatch surfaces as a
ChainDB.AddBlockValidation.InvalidBlocktrace inlogs/cardano-bp.log)
What this does NOT validate
- Byron-era code paths (chain boots in Conway)
- Hard-fork combinator era transitions (none occur during the soak)
- Multi-relay diffusion topologies (single hub)
- Plutus phase-2 / governance enactment — the soak itself submits only identical self-payments. Scripts, proposals, voting, and enactment are covered by the tx-zoo (
03,06,07,10,12) under a separate driver - Symmetric multi-forger chain selection (cardano-node is a validator here, not a forger — see "Historical note" at top)
- Mainnet-scale peer counts, NAT/firewall behaviour, or BGP-level routing
- Mithril snapshot import (covered by other tests)
Transaction validation (tx-zoo)
In addition to the soak (which exercises block production + diffusion under a steady-state load of identical self-payments), the devnet ships with a transaction zoo that exercises the full Conway tx surface against the same 3-node hub. Use this when you want to verify that the dugite mempool, forger, and validator accept every transaction class that mainnet allows.
./testnet/local-devnet/tx-zoo/run-all.sh --setup # one-time: keys + Plutus binaries
./testnet/local-devnet/tx-zoo/run-all.sh # run everything
./testnet/local-devnet/tx-zoo/run-all.sh 03-plutus # one category
./testnet/local-devnet/tx-zoo/run-all.sh --summary # totals from the last run
The zoo covers 12 categories (111 scripts total):
| Category | Scripts | Covers |
|---|---|---|
01-bookkeeping | 8 | simple-pay, multi-output, metadata CIP-20/CIP-25, validity intervals, required signers, treasury donation, tx chaining |
02-native-scripts | 7 | all/any/atLeast policies, time-locked, burns, pay-to-script, spend-from-script |
03-plutus | 14 | V1/V2/V3 spend, mint, inline datums, reference scripts, reference inputs, datum-hash reveal, collateral consumption |
04-stake | 7 | register, delegate, combined certs, deregister, pool register/retire, reward withdrawal |
05-governance-certs | 8 | DRep register/update/deregister, vote-delegation (DRep + always-abstain + always-no-confidence), CC hot-key authorisation, CC resignation |
06-proposals | 7 | Info, ParameterChange, HardForkInitiation, TreasuryWithdrawal, NoConfidence, UpdateCommittee, NewConstitution |
07-voting | 8 | DRep + SPO + CC yes/no/abstain |
08-negative | 19 | min-utxo violation, fee-too-low, expired TTL, insufficient collateral, double-spend, and other rejection paths |
09-cli-parity | 24 | runs cardano-cli against both sockets and diffs the answers |
10-gov-lifecycle | 5 | end-to-end proposal -> vote -> ratify -> enact |
11-mempool | 3 | mempool admission and eviction behaviour |
12-post-enactment | 1 | state after a governance action is enacted |
Each script submits through the dugite relay socket and waits for inclusion
at that same socket to guarantee diffusion + validation on the dugite path
before recording PASS/FAIL/SKIP into tx-zoo/state/results.csv.
Reading
09-cli-parity: it runscardano-cliagainst both sockets and diffs the responses — it never invokesdugite-cli. What it measures is dugite-node's LSQ responses. A failure on both sides is a harness bug, never a dugite-cli gap.
Requirements
- The devnet must be up via
./run.sh(the zoo refuses to start without all 3 sockets). - The Conway genesis must boot at protocol version >= 10 so Plutus V1/V2/V3
and Conway-only certs are admissible. The committed
shelley-spec.jsonalready setsprotocolVersion.major = 10; if you fork the spec to a lower version every script that touches Plutus or governance will record FAIL with "ScriptFailed: requires protocol >= N" from cardano-cli or the dugite mempool. - The zoo uses vendored always-true Plutus binaries for V1/V2/V3 scripts. Three categories (collateral, ref-scripts, inline-datum) run with these vendored versions; only the negative-path "insufficient collateral" needs a real V2 always-true script (also vendored).
Caveats
not-includedfailures: the zoo uses a single shared funding wallet (the genesis UTxO key) and runs scripts in lexical order. Each test consumes the largest UTxO at that wallet, so concurrent runs against the same socket will race. If you see intermittentnot-includedinresults.csvfor a tx that was added to the mempool, check that no other client is submitting to the same socket — the zoo expects exclusive access.- Rewards warmup:
reward-withdrawalcannot succeed until the delegated stake earns rewards, which requires at least two epoch boundaries after delegation. On the 400-slot epoch this means the script SKIPs for roughly the first ~13 minutes after a fresh devnet boot. The script records SKIP with reasonno-rewardsrather than failing.
Seated CC member
cardano-cli conway genesis create-testnet-data emits an empty
Constitutional Committee (members={}, threshold=0), which would make the
CC hot-key auth, CC resignation, and CC voting scripts non-runnable. To
provide full governance coverage out of the box, setup.sh generates a CC
cold/hot key pair at keys/cc-1/ and post-processes the generated
conway-genesis.json to seat that member with a 1-of-1 threshold and a
long expiry term (epoch 1000). tx-zoo/lib/keygen.sh then reuses those
same keys, so 05g-cc-hot-key-authorization, 05h-cc-resign,
07f-cc-vote-yes, and 07g-cc-vote-no operate on a genuine seated member
rather than an orphan keypair.
If you fork setup.sh or generate genesis through any other path, ensure
the resulting conway-genesis.json has a non-empty committee.members
map and a threshold matching that membership; otherwise those four
scripts will auto-SKIP with reason empty-committee / cc-not-authorized.
Results are appended to tx-zoo/state/results.csv (one row per script with
ts, name, status, txid, detail) and the per-script stderr lives under
tx-zoo/state/logs/<name>.log. After a failed run, re-run a single category
or script directly:
./testnet/local-devnet/tx-zoo/03-plutus/03a-spend-v1.sh
Tracking: see the GitHub issue linked from
docs/superpowers/specs/2026-05-16-local-testnet-design.md.
Kubernetes Deployment
Dugite ships a Helm chart at charts/dugite-node/ for deploying to Kubernetes as either a relay node or a block producer. Container images are published on every tagged release as multi-arch (linux/amd64, linux/arm64) images at ghcr.io/michaeljfazio/dugite.
Prerequisites
- Kubernetes 1.27+
- Helm 3.12+ (chart was tested against Helm 4.x)
- A
StorageClassthat supportsReadWriteOncepersistent volumes - (Optional) Prometheus Operator for
ServiceMonitorscraping
Quick Start
The chart is published as an OCI artifact on every tagged release. Install directly from the registry:
helm install dugite-relay \
oci://ghcr.io/michaeljfazio/charts/dugite-node \
--version 0.9.3 \
--set network.name=preview
The chart version (0.9.3) and the node version it deploys (appVersion 2.4.3) move independently — check charts/dugite-node/Chart.yaml for the
current pair. Omit --version to take the latest published chart.
Or install from a local checkout:
helm install dugite-relay ./charts/dugite-node \
--set network.name=preview
This will:
- Run a Mithril snapshot import (init container) with the Haskell ancillary state — bootstrap time drops from a multi-hour replay to ~15 minutes.
- Start the node syncing with the preview testnet.
- Create a 100 GiB persistent volume for the chain database.
- Expose Prometheus metrics on port 12796.
Chart Reference
Node role
The chart supports two deployment modes:
# Relay node (default)
role: relay
# Block producer
role: producer
Network selection
network:
name: preview # mainnet, preview, or preprod
port: 3001 # N2N port
hostAddr: "0.0.0.0"
diffusionMode: InitiatorAndResponder # use InitiatorOnly for BPs behind NAT
peerSharing: null # null = auto (on for relay, off for BP); set true/false to override
network.magic is derived automatically from network.name. Override only for private networks.
Persistence
persistence:
enabled: true
storageClass: "" # blank = cluster default; "-" = no storageClassName
size: 100Gi # preview ~16 GiB, preprod ~50 GiB, mainnet 150+ GiB
accessMode: ReadWriteOnce
existingClaim: "" # use an existing PVC
For mainnet, 200 GiB is a safe minimum once Conway-era state and the LSM UTxO backend are accounted for.
Resources
resources:
requests:
cpu: "1"
memory: 3Gi
limits:
cpu: "4"
memory: 16Gi
Soak-test RSS on preview is ~2.6 GiB; mainnet steady state is 8–10 GiB. Raise requests.memory to 4–6 GiB and limits.memory to 24–32 GiB for mainnet bulk sync.
Mithril import
mithril:
enabled: true # run Mithril import on first start (idempotent)
includeAncillary: true # download Haskell ledger state — drops bootstrap to ~15 min
Set mithril.includeAncillary: false to fall back to chunk-by-chunk replay. Trust model is documented in Mithril Ancillary.
Storage profile
storageProfile: high-memory # ultra-memory | high-memory (default) | low-memory | minimal
Match this to resources.limits.memory: high-memory ≈ 16 GiB, low-memory ≈ 8 GiB, minimal ≈ 4 GiB.
Metrics and monitoring
metrics:
enabled: true
port: 12796 # 12796 keeps dugite from colliding with cardano-node (12798)
compat: false # emit cardano_node_metrics_* aliases for legacy dashboards
require: false # treat metrics bind failure as fatal startup error
serviceMonitor:
enabled: false # set true if running Prometheus Operator
interval: 30s
labels: {}
When serviceMonitor.enabled is true the chart creates a ServiceMonitor resource for automatic Prometheus scraping. Enable compat: true to keep existing cardano-node Grafana dashboards working unmodified.
Available metrics include sync_progress_percent, blocks_applied_total, utxo_count, epoch_number, peers_connected, and more. See Monitoring for the full list.
UTxO RPC (gRPC) server — #672
rpc:
enabled: false # set true to enable the v1beta UTxO RPC API
port: 50051
host: "127.0.0.1" # set to 0.0.0.0 to expose via Service
service: false # expose RPC on the cluster Service (requires host: 0.0.0.0)
When rpc.enabled is true the deployment adds an rpc container port and the node starts the gRPC server. Setting rpc.service: true adds an rpc port to the Service and a NetworkPolicy rule (for the producer NetworkPolicy) allowing cross-pod gRPC access.
Consensus mode (genesis sync) — #535
consensusMode: "" # "" = praos (default); set "genesis" to opt in to Genesis sync
Currently the JSON config field ConsensusMode is not wired through to the runtime gate, so the chart passes the value via --consensus-mode on the CLI when set.
Logging
logging:
minSeverity: Info # Debug | Info | Notice | Warning | Error | Critical
rustLog: "info" # tracing_subscriber EnvFilter directive
format: text # text (human-readable) or json (structured for log shippers)
noColor: true # disable ANSI colors in container stdout
Liveness threshold
livenessThresholdSecs: 600 # /live returns 503 if no block applied in this window
This is passed via --liveness-threshold-secs. Set to 0 to make /live always return 200 (probes still pass even if the node has stalled).
Topology
topology:
bootstrapPeers:
- address: preview-node.play.dev.cardano.org
port: 3001
localRoots: []
publicRoots:
- accessPoints:
- address: preview-node.play.dev.cardano.org
port: 3001
advertise: false
useLedgerAfterSlot: 102729600 # mainnet=0, preview=102729600, preprod=76723200
Peer targets and governor tuning
These mirror cardano-node's defaults. Leave them alone unless you have a measured reason to change them.
network:
targetRootPeers: 60
targetActivePeers: 20
targetEstablishedPeers: 30
targetKnownPeers: 150
targetActiveBigLedgerPeers: 5
targetEstablishedBigLedgerPeers: 10
targetKnownBigLedgerPeers: 15
peerGovernor:
churnIntervalNormalSecs: 3300 # 55 min
churnIntervalSyncSecs: 900 # 15 min
stallDemotionCycles: 6 # each ~30s
errorDemotionThreshold: 5
Probes
livenessProbe:
httpGet: { path: /live, port: metrics }
initialDelaySeconds: 120
periodSeconds: 30
failureThreshold: 5
readinessProbe:
httpGet: { path: /health, port: metrics }
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
Both probes are automatically disabled when metrics.enabled=false, since
they target the metrics port. Liveness uses /live (forward progress —
restarts a wedged node) rather than /ready (sync progress), so a node that
is merely still syncing is never restart-looped. See
Monitoring for the endpoint semantics.
Security contexts
The chart runs as non-root with a read-only root filesystem by default:
podSecurityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile: { type: RuntimeDefault }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: [ALL] }
readOnlyRootFilesystem: true is safe because the chart mounts writable
volumes at /data/db, /ipc, and /tmp.
NetworkPolicy
networkPolicy:
enabled: true # only takes effect when role=producer
Set to false if your cluster has no NetworkPolicy controller — otherwise
the policy is written but silently unenforced, which is worse than not
having it.
Relay Node Deployment
A relay node connects to the Cardano network, syncs blocks, and serves them to connected peers and local clients.
Minimal relay
helm install dugite-relay ./charts/dugite-node \
--set network.name=mainnet \
--set persistence.size=200Gi
Relay with custom topology
helm install dugite-relay ./charts/dugite-node \
--set network.name=mainnet \
--set persistence.size=200Gi \
-f relay-values.yaml
relay-values.yaml:
topology:
bootstrapPeers:
- address: backbone.cardano.iog.io
port: 3001
- address: backbone.mainnet.cardanofoundation.org
port: 3001
- address: backbone.mainnet.emurgornd.com
port: 3001
localRoots:
- accessPoints:
- address: dugite-producer-dugite-node.default.svc.cluster.local
port: 3001
advertise: false
trustable: true
valency: 1
publicRoots:
- accessPoints:
- address: backbone.cardano.iog.io
port: 3001
- address: backbone.mainnet.cardanofoundation.org
port: 3001
advertise: false
useLedgerAfterSlot: 0
Relay with Prometheus Operator
helm install dugite-relay ./charts/dugite-node \
--set network.name=mainnet \
--set metrics.serviceMonitor.enabled=true \
--set metrics.serviceMonitor.labels.release=prometheus
Enable metrics.compat=true if your dashboards still reference cardano_node_metrics_* series.
Block Producer Deployment
A block producer creates blocks when elected as slot leader. It requires KES, VRF, and operational certificate keys.
Create keys Secret
kubectl create secret generic dugite-producer-keys \
--from-file=kes.skey=kes.skey \
--from-file=vrf.skey=vrf.skey \
--from-file=node.cert=node.cert
Deploy the producer
helm install dugite-producer ./charts/dugite-node \
--set role=producer \
--set network.name=mainnet \
--set producer.existingSecret=dugite-producer-keys \
--set persistence.size=200Gi \
--set network.diffusionMode=InitiatorOnly \
--set network.peerSharing=false
InitiatorOnly is the canonical block-producer diffusion mode — the BP only opens outbound connections to its relays.
Producer security
When role=producer, the chart automatically creates a NetworkPolicy that:
- Restricts N2N ingress to pods labeled
app.kubernetes.io/component: relayin the same namespace — the rule uses a barepodSelectorwith nonamespaceSelector, so a relay deployed to a different namespace will be blocked. - Allows metrics scraping (and, when
rpc.enabledandrpc.serviceare both true, RPC) from anywhere in the cluster. - Declares
policyTypes: [Ingress]only, so egress stays unrestricted and the BP can reach its relay(s).
Set networkPolicy.enabled=false to skip it — for example on a cluster with no NetworkPolicy controller, where an unenforced policy gives false assurance.
Block producers should never be exposed directly to the internet.
Producer + Relay architecture
A typical production deployment uses one or more relay nodes that shield the block producer:
graph LR
Internet[Cardano Network] --> R1[Relay 1]
Internet --> R2[Relay 2]
R1 --> BP[Block Producer]
R2 --> BP
BP -. blocks .-> R1
BP -. blocks .-> R2
Deploy both:
# Block producer
helm install dugite-producer ./charts/dugite-node \
--set role=producer \
--set network.name=mainnet \
--set producer.existingSecret=dugite-producer-keys \
-f producer-values.yaml
# Relay(s) pointing at the producer
helm install dugite-relay ./charts/dugite-node \
--set role=relay \
--set network.name=mainnet \
-f relay-values.yaml
producer-values.yaml:
network:
diffusionMode: InitiatorOnly
peerSharing: false
topology:
bootstrapPeers: []
localRoots:
- accessPoints:
- address: dugite-relay-dugite-node.default.svc.cluster.local
port: 3001
advertise: false
trustable: true
valency: 1
publicRoots: []
useLedgerAfterSlot: -1
Verifying the Deployment
Pod status:
kubectl get pods -l app.kubernetes.io/name=dugite-node
Logs:
kubectl logs -f deploy/dugite-relay-dugite-node
Query the node tip via N2C (the IPC socket is shared with the pod):
kubectl exec deploy/dugite-relay-dugite-node -- \
dugite-cli query tip \
--testnet-magic 2 \
--socket-path /ipc/node.sock
For mainnet, replace --testnet-magic 2 with --mainnet.
Metrics:
kubectl port-forward svc/dugite-relay-dugite-node 12796:12796
curl -s http://localhost:12796/metrics | grep sync_progress
UTxO RPC (if rpc.enabled=true):
kubectl port-forward svc/dugite-relay-dugite-node 50051:50051
grpcurl -plaintext localhost:50051 list
Configuration Reference
| Parameter | Default | Description |
|---|---|---|
role | relay | Node role: relay or producer |
image.repository | ghcr.io/michaeljfazio/dugite | Container image |
image.tag | Chart appVersion | Image tag |
replicaCount | 1 | Keep at 1 — a node owns its ChainDB exclusively and does not scale horizontally |
socketPath | /ipc/node.sock | N2C socket, shared with sidecars via the /ipc emptyDir |
network.name | preview | Network: mainnet, preview, preprod |
network.magic | null | Auto-derived from network.name; override only for private networks |
network.port | 3001 | N2N port |
network.diffusionMode | InitiatorAndResponder | InitiatorOnly for BPs behind NAT |
network.peerSharing | null | true/false to override the relay/BP default |
network.targetActivePeers | 20 | Peer governor targets (mirror cardano-node defaults) |
mithril.enabled | true | Run Mithril import on first start |
mithril.includeAncillary | true | Download Haskell ledger state (~15 min bootstrap) |
ledger.replayLimit | null | Max blocks to replay (null = unlimited) |
ledger.pipelineDepth | 150 | ChainSync pipeline depth |
storageProfile | high-memory | ultra-memory / high-memory / low-memory / minimal |
consensusMode | "" | Set "genesis" to opt in to Genesis sync (#535) |
livenessThresholdSecs | 600 | /live returns 503 after this idle window |
experimentalHardForksEnabled | false | Signal PV 11 0 in forged headers |
persistence.enabled | true | Enable persistent storage |
persistence.size | 100Gi | Volume size |
persistence.storageClass | "" | Blank = cluster default |
persistence.accessMode | ReadWriteOnce | |
persistence.existingClaim | "" | Use an existing PVC instead of creating one |
metrics.enabled | true | Enable Prometheus metrics |
metrics.port | 12796 | Metrics port (avoids cardano-node's 12798) |
metrics.compat | false | Emit cardano_node_metrics_* aliases |
metrics.serviceMonitor.enabled | false | Create a ServiceMonitor |
rpc.enabled | false | Enable UTxO RPC (gRPC) server |
rpc.port | 50051 | RPC port |
rpc.service | false | Expose RPC on the cluster Service |
logging.format | text | text or json |
logging.minSeverity | Info | Debug … Critical |
logging.rustLog | info | EnvFilter directive |
producer.existingSecret | "" | Secret with kes.skey / vrf.skey / node.cert |
producer.kesKey / vrfKey / operationalCertificate | "" | Inline key material (chart creates the Secret). Prefer existingSecret. |
networkPolicy.enabled | true | Create the producer NetworkPolicy (role=producer only) |
peerGovernor.churnIntervalNormalSecs | 3300 | Churn interval in steady state |
peerGovernor.churnIntervalSyncSecs | 900 | Churn interval while syncing |
service.type | ClusterIP | |
serviceAccount.create | true | |
resources.requests.cpu | 1 | CPU request |
resources.requests.memory | 3Gi | Memory request |
resources.limits.cpu | 4 | CPU limit |
resources.limits.memory | 16Gi | Memory limit |
CLI Overview
Dugite provides dugite-cli, a cardano-cli compatible command-line interface for interacting with a running Dugite node and managing keys, transactions, and governance.
Binary
dugite-cli [COMMAND] [OPTIONS]
Command Groups
| Command | Description |
|---|---|
address | Address generation and manipulation |
key | Payment and stake key generation (dugite extension — see below) |
transaction | Transaction building, signing, and submission |
query | Node queries (tip, UTxO, protocol parameters, etc.) |
stake-address | Stake address registration, delegation, and vote delegation |
stake-pool | Stake pool operations (key generation, registration, retirement certificates) |
governance | Conway governance (DRep, voting, proposals) |
node | Node key operations (cold keys, KES, VRF, operational certificates) |
byron | Byron-era key conversion commands (byron key ...) |
genesis | Genesis block/bundle commands (keys, delegation certs, genesis create) |
text-view | Decode a text-envelope file's CBOR representation |
The key command group (generate-payment-key, generate-stake-key,
verification-key-hash) is a dugite-only convenience extension with no
cardano-cli counterpart. The cardano-cli equivalents — address key-gen,
stake-address key-gen, and address key-hash — are also implemented, so
scripts written against cardano-cli work unchanged. See
Key Generation for the mapping.
Era Prefixes
cardano-cli 11 accepts commands only in their era-prefixed form, e.g.
cardano-cli conway stake-pool registration-certificate .... dugite accepts
both the era-prefixed form (conway, babbage, alonzo, mary,
allegra, shelley, latest) and the flat form (dugite-cli stake-pool registration-certificate ...) — every era prefix routes to the same
handler, since dugite is era-agnostic at the CLI surface. This makes dugite a
strict superset: any cardano-cli-compatible script works unchanged, and
existing dugite scripts using the flat form keep working too.
Common Patterns
Socket Path
Most commands that interact with a running node require --socket-path to specify the Unix domain socket:
dugite-cli query tip --socket-path ./node.sock
The default socket path is node.sock in the current directory.
Testnet Magic
When querying a node on a testnet, pass the --testnet-magic flag:
dugite-cli query tip --socket-path ./node.sock --testnet-magic 2
For mainnet, --testnet-magic is not needed (defaults to mainnet magic 764824073).
Text Envelope Format
Keys, certificates, and transactions are stored in the cardano-node "text envelope" JSON format:
{
"type": "PaymentSigningKeyShelley_ed25519",
"description": "Payment Signing Key",
"cborHex": "5820..."
}
This format is interchangeable with files produced by cardano-cli.
Output Files
Commands that produce artifacts use --out-file:
dugite-cli transaction build ... --out-file tx.body
dugite-cli transaction sign ... --out-file tx.signed
Help
Every command supports --help:
dugite-cli --help
dugite-cli transaction --help
dugite-cli transaction build --help
dugite-node Reference
dugite-node is the main Dugite node binary. The two subcommands used in
day-to-day operation are run (start the node) and mithril-import (import
a Mithril snapshot for fast initial sync), documented below.
The binary also ships several operator/debug subcommands not covered in
detail here: db info (database size and block count), dump-snapshot
(replay the chain and dump ledger state at epoch boundaries, for
cross-validation), verify-ledger-snapshot (byte-exact comparison of two
ledger snapshots), and snapshot-convert (convert a ledger snapshot between
the in-memory and LSM UTxO backends without a chain replay). Run
dugite-node <subcommand> --help for their flags.
run
Start the Dugite node:
dugite-node run [OPTIONS]
Options
| Flag | Default | Description |
|---|---|---|
--config | config/mainnet/config.json | Path to the node configuration file |
--topology | config/mainnet/topology.json | Path to the topology file |
--database-path | db | Path to the database directory |
--socket-path | node.sock | Unix domain socket path for N2C (local client) connections |
--port | 3001 | TCP port for N2N (node-to-node) connections |
--host-addr | 0.0.0.0 | Host address to bind to |
--metrics-port | Prometheus metrics port. If omitted, the config file's MetricsPort is used; if neither is set, defaults to 12798 | |
--no-metrics | false | Disable the Prometheus metrics server entirely. Equivalent to --metrics-port 0 |
--require-metrics | false | Make a metrics bind failure a fatal startup error (default: node continues if the port can't be bound) |
--rpc-host | UTxO RPC (gRPC) server bind address. Overrides Rpc.ListenAddr from the config file. Defaults to 127.0.0.1 when the server is enabled | |
--rpc-port | UTxO RPC (gRPC) server port. Overrides Rpc.Port; setting this implies enabling the RPC server. Defaults to 50051 when set via config | |
--no-rpc | false | Disable the UTxO RPC (gRPC) server entirely, overriding --rpc-host/--rpc-port/Rpc.Enabled |
--compat-metrics | false | Also emit cardano_node_metrics_* compatibility aliases alongside the native dugite_* metrics, for reuse of existing cardano-node Grafana dashboards |
--liveness-threshold-secs | 600 | Liveness threshold (seconds) for the /live HTTP endpoint; 0 disables it (always 200) |
--consensus-mode | Consensus mode override: praos or genesis (Ouroboros Genesis with GSM). When omitted, read from the config file's ConsensusMode field (default PraosMode) | |
--validate-all-blocks | false | Force full Phase-2 Plutus validation on all blocks, even during initial sync (normally only blocks at tip are fully validated) |
--skip-eagerly-validated-header-crypto | false | Skip apply-time header re-validation for headers that already passed eager per-peer validation. Off by default; see the flag's doc comment before enabling in production |
--dijkstra-genesis | Path to the Dijkstra-era genesis JSON file, overriding the config file's DijkstraGenesisFile (parsed but not yet applied to runtime protocol parameters) | |
--shelley-kes-key | Path to the KES signing key (enables block production) | |
--shelley-vrf-key | Path to the VRF signing key (enables block production) | |
--shelley-operational-certificate | Path to the operational certificate (enables block production) | |
--shelley-cold-key | Path to the cold signing key file, used for pool ID derivation | |
--log-output | stdout | Log output target: stdout, file, or journald. Can be specified multiple times. |
--log-format | text | Log format: text (human-readable) or json (structured). |
--log-level | info | Log level (trace, debug, info, warn, error). Overridden by RUST_LOG. |
--log-dir | logs | Directory for log files (used with --log-output file) |
--log-file-rotation | daily | Log file rotation strategy: daily, hourly, or never |
--log-no-color | false | Disable ANSI colors in stdout output |
--log-retention-days | 7 | Number of days to retain log files |
--stdout-overflow | drop | Channel-full policy for the non-blocking stdout writer: drop (keep going, count dropped lines) or block (lossless, but re-introduces blocking on the hot path) |
--mempool-max-tx | 16384 | Maximum number of transactions in the mempool |
--mempool-max-bytes | 536870912 | Maximum mempool size in bytes (default 512 MB) |
--snapshot-max-retained | 2 | Maximum number of ledger snapshots to retain on disk |
--snapshot-bulk-min-blocks | 50000 | Minimum blocks between bulk-sync snapshots |
--snapshot-bulk-min-secs | 360 | Minimum seconds between bulk-sync snapshots |
--storage-profile | high-memory | Storage profile: ultra-memory (32GB), high-memory (16GB), low-memory (8GB), or minimal (4GB) |
--immutable-index-type | Override block index type: in-memory or mmap | |
--utxo-backend | Override UTxO backend: in-memory or lsm | |
--utxo-memtable-size-mb | Override LSM memtable size in MB | |
--utxo-block-cache-size-mb | Override LSM block cache size in MB | |
--utxo-bloom-filter-bits | Override LSM bloom filter bits per key |
Relay Node (default)
Run as a relay node with no block production keys:
dugite-node run \
--config config/preview/config.json \
--topology config/preview/topology.json \
--database-path ./db-preview \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001
Block Producer
Run as a block producer by providing all three key/certificate paths:
dugite-node run \
--config config/preview/config.json \
--topology config/preview/topology.json \
--database-path ./db-preview \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001 \
--shelley-kes-key ./keys/kes.skey \
--shelley-vrf-key ./keys/vrf.skey \
--shelley-operational-certificate ./keys/opcert.cert
When all three block producer flags are provided, the node enters block production mode. The cold signing key is not needed at runtime — the cold verification key is extracted from the operational certificate, matching cardano-node behavior.
If any of the three flags is missing, the node runs in relay-only mode.
Environment Variables
| Variable | Default | Description |
|---|---|---|
DUGITE_PIPELINE_DEPTH | 300 | ChainSync pipeline depth (number of blocks requested ahead) |
RUST_LOG | info | Log level filter (e.g., debug, info, warn, dugite_node=debug). Overrides --log-level. |
See Logging for details on output targets, file rotation, and per-crate filtering.
Configuration File
The --config file follows the same JSON format as cardano-node. Key fields:
{
"Protocol": "Cardano",
"RequiresNetworkMagic": "RequiresMagic",
"ByronGenesisFile": "byron-genesis.json",
"ShelleyGenesisFile": "shelley-genesis.json",
"AlonzoGenesisFile": "alonzo-genesis.json",
"ConwayGenesisFile": "conway-genesis.json"
}
Genesis file paths are resolved relative to the directory containing the config file.
Metrics
When --metrics-port is non-zero, Prometheus metrics are served at http://localhost:<port>/metrics. See Monitoring for the full list of available metrics.
mithril-import
Import a Mithril snapshot for fast initial sync. This downloads and verifies a certified snapshot from a Mithril aggregator, then imports all blocks into the local database.
dugite-node mithril-import [OPTIONS]
Options
| Flag | Default | Description |
|---|---|---|
--network-magic | 764824073 | Network magic value |
--database-path | db | Path to the database directory |
--temp-dir | Temporary directory for download and extraction (uses system temp if omitted) | |
--mithril-genesis-vkey | Override the Mithril genesis verification key (JSON hex-encoded Ed25519 verification key string), for private networks | |
--skip-certificate-verification | false | Skip Mithril STM certificate chain verification (UNSAFE — testing only) |
--allow-stale-pparams | false | Continue the import even if the ancillary archive can't be downloaded, falling back to genesis-default protocol parameters at the imported tip. Not recommended for production |
--include-ancillary / --no-include-ancillary | true | Download and import the Mithril ancillary archive (Haskell ledger state at the immutable tip), dropping bootstrap time from multi-hour to ~15 minutes. --no-include-ancillary restores the pre-ancillary behavior of deriving ledger state entirely from chunk-by-chunk block replay — see Mithril Ancillary |
--log-output | stdout | Log output target: stdout, file, or journald. Can be specified multiple times. |
--log-format | text | Log format: text (human-readable) or json (structured). |
--log-level | info | Log level (trace, debug, info, warn, error). Overridden by RUST_LOG. |
--log-dir | logs | Directory for log files (used with --log-output file) |
--log-file-rotation | daily | Log file rotation strategy: daily, hourly, or never |
--log-no-color | false | Disable ANSI colors in stdout output |
--log-retention-days | 7 | Number of days to retain log files |
--stdout-overflow | drop | Channel-full policy for the non-blocking stdout writer: drop or block |
Network Magic Values
| Network | Magic |
|---|---|
| Mainnet | 764824073 |
| Preview | 2 |
| Preprod | 1 |
Example: Preview Testnet
dugite-node mithril-import \
--network-magic 2 \
--database-path ./db-preview
# Then start the node to sync from the snapshot to tip
dugite-node run \
--config config/preview/config.json \
--topology config/preview/topology.json \
--database-path ./db-preview \
--socket-path ./node.sock
The import process:
- Downloads the latest snapshot from the Mithril aggregator
- Verifies the snapshot digest (SHA256)
- Extracts and parses immutable chunk files
- Imports blocks into ChainDB with CRC32 verification
- Supports resume — skips blocks already in the database
On preview testnet, importing ~4M blocks takes approximately 2 minutes.
Key Generation
Dugite CLI supports generating all key types needed for Cardano operations.
The
keygroup is a dugite extension.key generate-payment-key,key generate-stake-key, andkey verification-key-hashhave no cardano-cli counterpart — they are additive convenience commands. The cardano-cli equivalents are also implemented and produce compatible output:
dugite extension cardano-cli equivalent key generate-payment-keyaddress key-genkey generate-stake-keystake-address key-genkey verification-key-hashaddress key-hash/stake-address key-hashScripts written against cardano-cli never need the
keygroup; it exists because it is convenient and already in use.
Payment Keys
Generate an Ed25519 key pair for payments:
dugite-cli key generate-payment-key \
--signing-key-file payment.skey \
--verification-key-file payment.vkey
Output files:
payment.skey— Payment signing key (keep secret)payment.vkey— Payment verification key (safe to share)
The cardano-cli-compatible equivalent is dugite-cli address key-gen --verification-key-file payment.vkey --signing-key-file payment.skey.
Stake Keys
Generate an Ed25519 key pair for staking:
dugite-cli key generate-stake-key \
--signing-key-file stake.skey \
--verification-key-file stake.vkey
Output files:
stake.skey— Stake signing keystake.vkey— Stake verification key
The cardano-cli-compatible equivalent is dugite-cli stake-address key-gen --verification-key-file stake.vkey --signing-key-file stake.skey (see
Stake Address Commands).
Verification Key Hash
Compute the Blake2b-224 hash of any verification key:
dugite-cli key verification-key-hash \
--verification-key-file payment.vkey
This outputs the 28-byte key hash in hexadecimal, used in addresses and certificates.
Only Ed25519 verification-key envelope types are accepted (payment, stake,
stake pool, genesis, genesis-delegate, genesis-UTxO, DRep, and CC cold/hot
verification keys). Signing keys and KES/VRF verification keys are rejected
with an error naming the offending envelope type — VRF key hashes use a
different convention and are computed with node key-hash-VRF instead (see
Node Commands).
DRep Keys
Generate keys for a Delegated Representative (Conway governance):
dugite-cli governance drep key-gen \
--signing-key-file drep.skey \
--verification-key-file drep.vkey
Get the DRep ID:
# Bech32 format (default)
dugite-cli governance drep id \
--drep-verification-key-file drep.vkey
# Hex format
dugite-cli governance drep id \
--drep-verification-key-file drep.vkey \
--output-format hex
Node Keys
See Node Commands for the full flag reference, including
--key-output-bech32 / --key-output-text-envelope and the canonical
--operational-certificate-issue-counter-file spelling. The short version:
Cold Keys
Generate cold keys and an operational certificate issue counter:
dugite-cli node key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter
--operational-certificate-issue-counter-file is the cardano-cli-canonical
spelling; --operational-certificate-issue-counter and
--operational-certificate-counter-file are accepted as aliases.
KES Keys
Generate Key Evolving Signature keys (rotated periodically). The canonical
cardano-cli subcommand casing is key-gen-KES (cardano-cli rejects
lowercase); dugite additionally accepts key-gen-kes as a backward-compatible
alias:
dugite-cli node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
VRF Keys
Generate Verifiable Random Function keys (for slot leader election). Canonical
casing is key-gen-VRF, with key-gen-vrf accepted as an alias:
dugite-cli node key-gen-VRF \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
Operational Certificate
Issue an operational certificate binding the cold key to the current KES key:
dugite-cli node issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter \
--kes-period 400 \
--out-file opcert.cert
Address Generation
Payment Address
Build a payment address from keys:
# Enterprise address (no staking)
dugite-cli address build \
--payment-verification-key-file payment.vkey \
--testnet-magic 2
# Base address (with staking)
dugite-cli address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--testnet-magic 2
# Mainnet address
dugite-cli address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--mainnet
--mainnet and --testnet-magic <NATURAL> are mutually exclusive
(cardano-cli compatible). Instead of a key file, the payment and stake keys
can be passed inline as a bech32 or hex string:
dugite-cli address build \
--payment-verification-key "addr_vk1..." \
--stake-verification-key "stake_vk1..." \
--mainnet
dugite additionally accepts a --network mainnet|testnet flag (its own
extension, predating --mainnet/--testnet-magic) and, when none of
--mainnet, --testnet-magic, or --network is given, falls back to the
CARDANO_NODE_NETWORK_ID environment variable (mainnet or a magic number),
matching cardano-cli. Resolution order: explicit flags, then --network,
then CARDANO_NODE_NETWORK_ID, then mainnet. An unrecognized value from any
of these sources is a hard error naming the accepted forms — it never falls
back to testnet silently.
Key File Format
All keys are stored in the cardano-node text envelope format:
{
"type": "PaymentSigningKeyShelley_ed25519",
"description": "Payment Signing Key",
"cborHex": "5820a1b2c3d4..."
}
The cborHex field contains the CBOR-encoded key bytes. The type field identifies the key type and is used for validation when loading keys.
Key files generated by Dugite are compatible with cardano-cli and vice versa.
Complete Workflow Example
Generate all keys needed for a basic wallet:
# 1. Generate payment keys
dugite-cli key generate-payment-key \
--signing-key-file payment.skey \
--verification-key-file payment.vkey
# 2. Generate stake keys
dugite-cli key generate-stake-key \
--signing-key-file stake.skey \
--verification-key-file stake.vkey
# 3. Build a testnet address
dugite-cli address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--testnet-magic 2
# 4. Get the payment key hash
dugite-cli key verification-key-hash \
--verification-key-file payment.vkey
Transactions
Dugite CLI supports the full transaction lifecycle: building, signing, submitting, and inspecting transactions.
Building a Transaction
dugite-cli transaction build \
--tx-in <tx_hash>#<index> \
--tx-out <address>+<lovelace> \
--change-address <address> \
--fee <lovelace> \
--out-file tx.body
transaction build-raw accepts the identical flag set and produces
identical output — it exists only so scripts that call cardano-cli transaction build-raw work unchanged.
Arguments
| Argument | Description |
|---|---|
--tx-in | Transaction input in tx_hash#index format. Can be specified multiple times |
--tx-out | Transaction output in address+lovelace format. Can be specified multiple times |
--change-address | Address to receive change (required for auto-balance mode) |
--fee | Fee in lovelace. If omitted and --socket-path is set, the fee is computed automatically (auto-balance mode, see below); if omitted without --socket-path, defaults to 200000 |
--ttl | Time-to-live slot number (optional) |
--certificate-file | Path to a certificate file to include (can be repeated) |
--withdrawal | Withdrawal in stake_address+lovelace format (can be repeated) |
--metadata-json-file | Path to a JSON metadata file (optional) |
--out-file | Output file for the transaction body |
Plutus and Conway flags
| Argument | Description |
|---|---|
--tx-in-script-file | Plutus script (text envelope, PlutusScriptV1/V2/V3) to attach to the most recently specified --tx-in. The Nth occurrence pairs with the Nth --tx-in by declaration order |
--tx-in-datum-file | Datum JSON file (cardano-cli PlutusData schema) for the script-bearing input at the same position |
--tx-in-redeemer-file | Redeemer JSON file (same schema) for the script-bearing input at the same position |
--tx-in-execution-units | Execution units budget for the script-bearing input, format mem,steps |
--tx-in-collateral | Collateral input for Plutus scripts, format tx_hash#index (can be repeated) |
--required-signer-hash | Required signer key hash, hex (can be repeated) |
--mint | Mint/burn tokens, format policy_id.asset_name+quantity or ...-quantity to burn (can be repeated) |
--read-only-tx-in-reference | Reference input visible to Plutus scripts but not consumed (CIP-31), format tx_hash#index (can be repeated) |
--tx-out-inline-datum-value | Inline datum for a transaction output (CIP-32), format INDEX:JSON or bare JSON (defaults to output 0) |
--tx-out-inline-datum-file | Inline datum file for a transaction output (CIP-32), format INDEX:FILE or bare FILE |
--tx-out-reference-script-file | Reference script for a transaction output (CIP-33), format INDEX:FILE or bare FILE |
--vote-file | Vote file to include (Conway governance, can be repeated) |
--proposal-file | Governance proposal file to include (Conway governance, can be repeated) |
--calculate-plutus-script-cost | Evaluate Plutus script execution costs and write the result to a JSON file. Requires --socket-path to evaluate against live ledger state |
Auto-balance mode
When --socket-path is provided and --fee is not explicitly set,
transaction build connects to the node, queries UTxO values for the given
inputs and the current protocol parameters, computes the fee automatically,
derives a change output at --change-address, and writes a balanced
transaction — matching cardano-cli transaction build's behavior (as
opposed to build-raw's fully manual fee/change accounting):
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--socket-path ./node.sock \
--testnet-magic 2 \
--out-file tx.body
| Argument | Description |
|---|---|
--socket-path | Path to the node's Unix domain socket. Enables auto-balance mode when --fee is omitted |
--mainnet | Use mainnet (network magic 764824073) |
--testnet-magic | Testnet network magic (e.g. 2 for preview, 1 for preprod) |
Example: Simple ADA Transfer
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--ttl 50000000 \
--out-file tx.body
Multi-Asset Outputs
To include native tokens in an output, use the extended format:
address+lovelace+"policy_id.asset_name quantity"
Example:
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out 'addr_test1qz...+2000000+"a1b2c3...d4e5f6.4d79546f6b656e 100"' \
--change-address "addr_test1qp..." \
--fee 200000 \
--out-file tx.body
Multiple tokens can be separated with + inside the quoted string:
"policy1.asset1 100+policy2.asset2 50"
Including Certificates
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--certificate-file stake-reg.cert \
--certificate-file stake-deleg.cert \
--out-file tx.body
Including Metadata
Create a metadata JSON file with integer keys:
{
"674": {
"msg": ["Hello, Cardano!"]
}
}
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--metadata-json-file metadata.json \
--out-file tx.body
Signing a Transaction
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--out-file tx.signed
Multiple signing keys can be provided:
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--out-file tx.signed
Submitting a Transaction
dugite-cli transaction submit \
--tx-file tx.signed \
--socket-path ./node.sock
The node validates the transaction (Phase-1 and Phase-2 for Plutus transactions) and, if valid, adds it to the mempool for propagation.
Viewing a Transaction
dugite-cli transaction view --tx-file tx.signed
Output includes:
- Transaction type
- CBOR size
- Transaction hash
- Number of inputs and outputs
- Fee
- TTL (if set)
Transaction ID
Compute the transaction hash:
dugite-cli transaction txid --tx-file tx.body
Works with both transaction body files and signed transaction files.
Calculate Minimum Fee
dugite-cli transaction calculate-min-fee \
--tx-body-file tx.body \
--witness-count 2 \
--protocol-params-file protocol-params.json
The fee calculation accounts for:
- Base fee:
txFeeFixed + txFeePerByte * tx_size - Script execution:
executionUnitPrices * total_ExUnitsfor any Plutus witnesses - Reference script surcharge: CIP-0112 tiered fee for reference scripts (25KiB tiers, 1.2x multiplier per tier)
To get the current protocol parameters:
dugite-cli query protocol-parameters \
--socket-path ./node.sock \
--out-file protocol-params.json
Calculate Minimum Required UTxO
Compute the minimum lovelace required for a transaction output to satisfy the minUTxOValue protocol parameter:
dugite-cli transaction calculate-min-required-utxo \
--protocol-params-file protocol-params.json \
--tx-out "addr_test1qz...+0+\"policy1.asset1 100\""
Output:
Minimum required lovelace: 1724100
This is particularly useful when constructing outputs that carry native tokens, since the minimum lovelace depends on the byte-size of the value bundle (number of policy IDs, asset names, and quantities).
Creating Witnesses
For multi-signature workflows, you can create witnesses separately and assemble them:
Create a Witness
dugite-cli transaction witness \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--out-file payment.witness
Assemble a Transaction
dugite-cli transaction assemble \
--tx-body-file tx.body \
--witness-file payment.witness \
--witness-file stake.witness \
--out-file tx.signed
Policy ID
Compute the policy ID (Blake2b-224 hash) of a native script:
dugite-cli transaction policyid --script-file policy.script
Hash Script Data
Compute the script-data hash (datum + redeemers + language views) used in a
transaction's scriptDataHash field:
dugite-cli transaction hash-script-data \
--datum-file datum.json \
--redeemer-file redeemer.json
| Argument | Description |
|---|---|
--datum-file | Datum JSON file (optional) |
--redeemer-file | Redeemer JSON file (optional) |
--script-data-file | Script data JSON file (optional) |
Complete Workflow
# 1. Query UTxOs to find inputs
dugite-cli query utxo \
--address addr_test1qz... \
--socket-path ./node.sock \
--testnet-magic 2
# 2. Get protocol parameters for fee calculation
dugite-cli query protocol-parameters \
--socket-path ./node.sock \
--testnet-magic 2 \
--out-file pp.json
# 3. Build the transaction
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qr...+5000000" \
--change-address "addr_test1qz..." \
--fee 200000 \
--out-file tx.body
# 4. Calculate the exact fee
dugite-cli transaction calculate-min-fee \
--tx-body-file tx.body \
--witness-count 1 \
--protocol-params-file pp.json
# 5. Rebuild with the correct fee (repeat step 3 with updated --fee)
# 6. Sign
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--out-file tx.signed
# 7. Submit
dugite-cli transaction submit \
--tx-file tx.signed \
--socket-path ./node.sock
Queries
Dugite CLI provides a comprehensive set of queries against a running node via the N2C (Node-to-Client) protocol over a Unix domain socket.
Chain Tip
Query the current chain tip:
dugite-cli query tip --socket-path ./node.sock
For testnets:
dugite-cli query tip --socket-path ./node.sock --testnet-magic 2
Output:
{
"slot": 73429851,
"hash": "a1b2c3d4e5f6...",
"block": 2847392,
"epoch": 170,
"era": "Conway",
"syncProgress": "99.87"
}
UTxO Query
Query UTxOs at a specific address:
dugite-cli query utxo \
--address addr_test1qz... \
--socket-path ./node.sock \
--testnet-magic 2
Alternatively, query one or more specific UTxOs by transaction input
reference (--tx-in may be repeated), or dump the entire UTxO set with
--whole (warning: very large on mainnet):
dugite-cli query utxo \
--tx-in "abc123...#0" \
--tx-in "def456...#1" \
--socket-path ./node.sock \
--testnet-magic 2
dugite-cli query utxo --whole --socket-path ./node.sock
Exactly one of --address, --tx-in, or --whole must be provided.
Output:
TxHash#Ix Datum Lovelace
------------------------------------------------------------------------------------------------
a1b2c3d4...#0 no 5000000
e5f6a7b8...#1 yes 10000000
Total UTxOs: 2
Protocol Parameters
Query current protocol parameters:
# Print to stdout
dugite-cli query protocol-parameters \
--socket-path ./node.sock
# Save to file
dugite-cli query protocol-parameters \
--socket-path ./node.sock \
--out-file protocol-params.json
The output is a JSON object containing all active protocol parameters, including fee settings, execution unit limits, and governance thresholds.
Stake Distribution
Query the stake distribution across all registered pools:
dugite-cli query stake-distribution \
--socket-path ./node.sock
Output is JSON by default (matching cardano-cli), keyed by pool ID hex with
each pool's stake expressed as an exact num/den fraction of total active
stake; pass --output-text for the human-readable table below, or
--out-file to write to a file instead of stdout.
JSON output (default):
{
"<pool_id_hex>": {
"poolId": "<pool_id_hex>",
"stakeFraction": "1/8523"
}
}
--output-text output:
PoolId Stake Fraction
--------------------------------------------------------------------------------
<pool_id_hex> 0.0001173413
Total pools: 3200
Stake Address Info
Query delegation and rewards for a stake address:
dugite-cli query stake-address-info \
--address stake_test1uz... \
--socket-path ./node.sock \
--testnet-magic 2
Output:
[
{
"address": "stake_test1uz...",
"delegation": "pool1abc...",
"rewardAccountBalance": 5234000
}
]
Stake Pools
List the IDs of all registered stake pools (use Pool Parameters below for the pledge/cost/margin of a specific pool):
dugite-cli query stake-pools \
--socket-path ./node.sock
Output is a JSON array of bech32 pool IDs by default (matching cardano-cli),
sorted by raw hash bytes; --output-text prints the same IDs
newline-separated instead. --out-file writes to a file instead of stdout.
--output-yaml is accepted by the flag parser but not yet implemented — it
currently errors at runtime.
JSON output (default):
[
"pool1abc...",
"pool1def..."
]
Pool Parameters
Query detailed parameters for a specific pool:
dugite-cli query pool-params \
--socket-path ./node.sock \
--stake-pool-id pool1abc...
Non-Myopic Member Rewards
Query expected (non-myopic) member rewards for hypothetical delegator
stakes, matching cardano-cli query non-myopic-member-rewards. Returns the
expected lovelace reward for each requested stake amount against every
registered pool, assuming ideal performance — used as input to pool-ranking
tools:
dugite-cli query non-myopic-member-rewards \
--socket-path ./node.sock \
--stake 1000000000
--stake may be repeated for multiple hypothetical stake values (in
lovelace); it defaults to 1 ADA when omitted.
Stake Snapshots
Query the mark/set/go stake snapshots:
dugite-cli query stake-snapshot \
--socket-path ./node.sock
# Filter by pool
dugite-cli query stake-snapshot \
--socket-path ./node.sock \
--stake-pool-id pool1abc...
Governance State (Conway)
Query the overall governance state:
dugite-cli query gov-state --socket-path ./node.sock
Output is JSON by default (matching cardano-cli); pass --output-text for
the human-readable summary shown below, or --out-file to write to a file.
Output:
Governance State (Conway)
========================
Treasury: 1234567890 ADA
Registered DReps: 456
Committee Members: 7
Active Proposals: 12
Proposals:
Type TxId Yes No Abstain
----------------------------------------------------
InfoAction a1b2c3#0 42 3 5
TreasuryWithdrawals d4e5f6#1 28 12 8
DRep State (Conway)
Query registered DReps. Exactly one DRep selector is required — there is no bare "all DReps" default:
# All DReps
dugite-cli query drep-state --all-dreps --socket-path ./node.sock
# Specific DRep by key hash
dugite-cli query drep-state \
--socket-path ./node.sock \
--drep-key-hash a1b2c3d4...
| Flag | Description |
|---|---|
--all-dreps | Query for all DReps |
--drep-key-hash | Filter by DRep key hash (28-byte blake2b-224 hex) |
--drep-script-hash | Filter by DRep script hash (28-byte hex) |
--drep-verification-key | Derive the DRep key hash from a verification key hex string |
--drep-verification-key-file | Derive the DRep key hash from a verification key text-envelope file |
--include-stake | Include each DRep's delegated stake in the response |
--output-json | Format output as JSON (the cardano-cli default) |
--output-yaml | Format output as YAML (mutually exclusive with --output-json) |
--out-file | Optional output file. Default is stdout |
These selector flags are mutually exclusive — pass exactly one.
Output:
DRep State (Conway)
===================
Total DReps: 456
Credential Hash Deposit (ADA) Epoch
--------------------------------------------------------------------------------------------
a1b2c3d4... 500 412
Anchor: https://example.com/drep-metadata.json
Committee State (Conway)
Query the constitutional committee:
dugite-cli query committee-state --socket-path ./node.sock
Output:
Constitutional Committee State (Conway)
=======================================
Active Members: 7
Resigned Members: 1
Cold Credential Hot Credential
--------------------------------------------------------------------------------------------------------------------------------------
a1b2c3d4... e5f6a7b8...
Resigned:
d4e5f6a7...
Transaction Mempool
Query the node's transaction mempool:
# Mempool info (size, capacity, tx count)
dugite-cli query tx-mempool info --socket-path ./node.sock
# Check if a specific transaction is in the mempool
dugite-cli query tx-mempool has-tx \
--socket-path ./node.sock \
--tx-id a1b2c3d4...
Info output:
Mempool snapshot at slot 73429851:
Capacity: 2000000 bytes
Size: 45320 bytes
Transactions: 12
Treasury
Query the treasury balance (matches cardano-cli query treasury, which
reports treasury only — not reserves):
dugite-cli query treasury --socket-path ./node.sock
The default output is the bare lovelace integer (cardano-cli-compatible):
1234567890000000
--output-text prints a human-readable summary that includes both treasury
and reserves:
Account State
=============
Treasury: 1234567890000000 lovelace (1234567 ADA)
Reserves: 9876543210000000 lovelace (9876543 ADA)
--out-file writes the selected format to a file instead of stdout.
Constitution (Conway)
Query the current constitution:
dugite-cli query constitution --socket-path ./node.sock
Output:
Constitution
============
URL: https://constitution.gov/hash.json
Data Hash: a1b2c3d4e5f6...
Script Hash: none
Ratification State (Conway)
Query the ratification state (enacted/expired proposals from the most recent epoch transition):
dugite-cli query ratify-state --socket-path ./node.sock
Output:
Ratification State
==================
Enacted proposals: 1
a1b2c3d4e5f6...#0
Expired proposals: 2
d4e5f6a7b8c9...#1
e5f6a7b8c9d0...#0
Delayed: false
Governance Proposals (Conway)
Query active governance action proposals, matching cardano-cli query proposals:
# All live proposals
dugite-cli query proposals --socket-path ./node.sock
# Filter to a specific proposal
dugite-cli query proposals \
--socket-path ./node.sock \
--governance-action-tx-id a1b2c3d4... \
--governance-action-index 0
| Flag | Description |
|---|---|
--all-proposals | Return all proposals (default) |
--governance-action-tx-id | Filter by governance action tx ID |
--governance-action-index | Filter by governance action index (requires --governance-action-tx-id) |
--out-file | Optional output file. Default is stdout |
Slot Number
Convert a wall-clock time to a Cardano slot number:
dugite-cli query slot-number \
--socket-path ./node.sock \
--testnet-magic 2 \
--utc-time "2026-03-20T12:00:00Z"
Output:
Slot: 73851200
This is useful for computing TTL values or verifying that a specific point in time falls within a given epoch.
KES Period Info
Query KES period information for an operational certificate:
dugite-cli query kes-period-info \
--socket-path ./node.sock \
--op-cert-file opcert.cert
Unlike the other queries above, the default output here is the
human-readable text summary shown below; pass --output-json for JSON, or
--out-file to write to a file.
Output:
KES Period Info
===============
On-chain: yes
Operational certificate counter on-chain: 3
Certificate issue counter: 3
Current KES period: 418
Operational certificate start KES period: 418
KES max evolutions: 62
KES periods remaining: 62
Node start time: 2026-03-19T08:00:00Z
KES key expiry: 2026-09-14T08:00:00Z
Use this command to verify that a KES key is current and to determine when rotation is needed.
Leadership Schedule
Compute the slots a stake pool is expected to mint a block in, matching
cardano-cli query leadership-schedule. This queries the running node for
live stake and epoch state (via --socket-path) rather than taking manual
stake/coefficient inputs:
dugite-cli query leadership-schedule \
--socket-path ./node.sock \
--testnet-magic 2 \
--genesis config/preview/shelley-genesis.json \
--stake-pool-id pool1abc... \
--vrf-signing-key-file vrf.skey \
--current
| Flag | Required | Description |
|---|---|---|
--socket-path | No (default node.sock) | Path to the node socket. Overrides CARDANO_NODE_SOCKET_PATH |
--mainnet | No | Use the mainnet magic ID (mutually exclusive with --testnet-magic) |
--testnet-magic | No | Testnet magic ID |
--genesis | Yes | Shelley genesis file path |
--stake-pool-id | No | Stake pool ID (hex-encoded hash) |
--cold-verification-key-file | No | Path to the cold verification key file |
--vrf-signing-key-file | Yes | Path to the VRF signing key |
--current | No | Leadership schedule for the current epoch (mutually exclusive with --next) |
--next | No | Leadership schedule for the following epoch |
--output-json | No | Format output as JSON (default) |
--output-text | No | Format output as text |
--out-file | No | Optional output file. Default is stdout |
Ledger State (Debug)
Dump the raw ledger state (debug endpoint):
dugite-cli query ledger-state \
--socket-path ./node.sock \
--out-file ledger-state.cbor
| Flag | Description |
|---|---|
--out-file | Optional output file. Default is stdout |
Protocol State (Debug)
Dump the raw protocol (consensus) state — includes the KES evolving nonce, candidate nonce, and epoch nonce (debug endpoint):
# Raw CBOR hex (default)
dugite-cli query protocol-state --socket-path ./node.sock
# JSON, matching cardano-cli's --output-json
dugite-cli query protocol-state \
--socket-path ./node.sock \
--output-json
| Flag | Description |
|---|---|
--output-json | Render the response as JSON (matches cardano-cli's query protocol-state --output-json). Without this flag, dugite emits the raw CBOR as hex |
--out-file | Optional output file. Default is stdout |
Stake Address Commands
The dugite-cli stake-address subcommands manage stake key generation, reward address construction, and certificate creation for staking operations.
key-gen
Generate a stake key pair:
dugite-cli stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | Yes | Output path for the stake verification key |
--signing-key-file | Yes | Output path for the stake signing key |
build
Build a stake (reward) address from a stake verification key:
dugite-cli stake-address build \
--stake-verification-key-file stake.vkey \
--network testnet
| Flag | Required | Default | Description |
|---|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key | |
--network | No | mainnet | Network: mainnet or testnet |
--out-file | No | Output file (prints to stdout if omitted) |
registration-certificate
Create a stake address registration certificate:
# Conway era (with deposit)
dugite-cli stake-address registration-certificate \
--stake-verification-key-file stake.vkey \
--key-reg-deposit-amt 2000000 \
--out-file stake-reg.cert
# Legacy Shelley era (no deposit parameter)
dugite-cli stake-address registration-certificate \
--stake-verification-key-file stake.vkey \
--out-file stake-reg.cert
| Flag | Required | Description |
|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key |
--key-reg-deposit-amt | No | Deposit amount in lovelace (Conway era; omit for legacy Shelley cert) |
--out-file | Yes | Output path for the certificate |
The deposit amount should match the current stakeAddressDeposit protocol parameter (typically 2 ADA = 2000000 lovelace).
deregistration-certificate
Create a stake address deregistration certificate to reclaim the deposit:
dugite-cli stake-address deregistration-certificate \
--stake-verification-key-file stake.vkey \
--key-reg-deposit-amt 2000000 \
--out-file stake-dereg.cert
| Flag | Required | Description |
|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key |
--key-reg-deposit-amt | No | Deposit refund amount (Conway era; omit for legacy Shelley cert) |
--out-file | Yes | Output path for the certificate |
delegation-certificate
Create a stake delegation certificate to delegate to a stake pool:
dugite-cli stake-address delegation-certificate \
--stake-verification-key-file stake.vkey \
--stake-pool-id pool1abc... \
--out-file delegation.cert
| Flag | Required | Description |
|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key |
--stake-pool-id | Yes | Pool ID to delegate to (bech32 or hex) |
--out-file | Yes | Output path for the certificate |
vote-delegation-certificate
Create a vote delegation certificate (Conway era) to delegate voting power to a DRep:
# Delegate to a specific DRep
dugite-cli stake-address vote-delegation-certificate \
--stake-verification-key-file stake.vkey \
--drep-verification-key-file drep.vkey \
--out-file vote-deleg.cert
# Delegate to always-abstain
dugite-cli stake-address vote-delegation-certificate \
--stake-verification-key-file stake.vkey \
--always-abstain \
--out-file vote-deleg.cert
# Delegate to always-no-confidence
dugite-cli stake-address vote-delegation-certificate \
--stake-verification-key-file stake.vkey \
--always-no-confidence \
--out-file vote-deleg.cert
| Flag | Required | Description |
|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key |
--drep-verification-key-file | No | DRep verification key file (mutually exclusive with --always-abstain/--always-no-confidence) |
--always-abstain | No | Use the special always-abstain DRep |
--always-no-confidence | No | Use the special always-no-confidence DRep |
--out-file | Yes | Output path for the certificate |
key-hash
Get the Blake2b-224 hash of a stake verification key:
dugite-cli stake-address key-hash \
--stake-verification-key-file stake.vkey
| Flag | Required | Description |
|---|---|---|
--stake-verification-key-file | Yes | Path to the stake verification key |
This is the cardano-cli-compatible equivalent of dugite-cli key verification-key-hash --verification-key-file stake.vkey (see
Key Generation).
Complete Staking Workflow
# 1. Generate stake keys
dugite-cli stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey
# 2. Create registration certificate
dugite-cli stake-address registration-certificate \
--stake-verification-key-file stake.vkey \
--key-reg-deposit-amt 2000000 \
--out-file stake-reg.cert
# 3. Create delegation certificate
dugite-cli stake-address delegation-certificate \
--stake-verification-key-file stake.vkey \
--stake-pool-id pool1abc... \
--out-file delegation.cert
# 4. Submit both in a single transaction
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--certificate-file stake-reg.cert \
--certificate-file delegation.cert \
--out-file tx.body
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--out-file tx.signed
dugite-cli transaction submit \
--tx-file tx.signed \
--socket-path ./node.sock
Stake Pool Commands
The dugite-cli stake-pool subcommands manage stake pool key generation, pool registration, and operational certificate issuance.
key-gen
Generate pool cold keys and an operational certificate counter:
dugite-cli stake-pool key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-counter-file opcert.counter
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Output path for the cold verification key |
--cold-signing-key-file | Yes | Output path for the cold signing key |
--operational-certificate-counter-file | Yes | Output path for the opcert issue counter |
id
Get the pool ID (Blake2b-224 hash of the cold verification key):
dugite-cli stake-pool id \
--cold-verification-key-file cold.vkey
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Path to the cold verification key |
vrf-key-gen
Generate a VRF key pair:
dugite-cli stake-pool vrf-key-gen \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | Yes | Output path for the VRF verification key |
--signing-key-file | Yes | Output path for the VRF signing key |
kes-key-gen
Generate a KES key pair:
dugite-cli stake-pool kes-key-gen \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | Yes | Output path for the KES verification key |
--signing-key-file | Yes | Output path for the KES signing key |
issue-op-cert
Issue an operational certificate:
dugite-cli stake-pool issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter \
--kes-period 400 \
--out-file opcert.cert
| Flag | Required | Description |
|---|---|---|
--kes-verification-key-file | Yes | Path to the KES verification key |
--cold-signing-key-file | Yes | Path to the cold signing key |
--operational-certificate-issue-counter-file | Yes | Path to the opcert issue counter. Same spellings as node issue-op-cert: --operational-certificate-issue-counter and the legacy --operational-certificate-counter-file are accepted as aliases |
--kes-period | Yes | Current KES period |
--out-file | Yes | Output path for the operational certificate |
This is the same underlying implementation as node issue-op-cert (see
Node Commands) — the two subcommands are interchangeable.
registration-certificate
Create a stake pool registration certificate:
dugite-cli stake-pool registration-certificate \
--cold-verification-key-file cold.vkey \
--vrf-verification-key-file vrf.vkey \
--pledge 500000000 \
--cost 340000000 \
--margin 0.02 \
--reward-account-verification-key-file stake.vkey \
--pool-owner-verification-key-file stake.vkey \
--single-host-pool-relay "relay.example.com:3001" \
--metadata-url "https://example.com/pool-metadata.json" \
--metadata-hash "a1b2c3d4..." \
--out-file pool-reg.cert
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Path to the cold verification key |
--vrf-verification-key-file | Yes | Path to the VRF verification key |
--pledge | Yes | Pledge amount in lovelace |
--cost | Yes | Fixed cost per epoch in lovelace |
--margin | Yes | Pool margin (0.0 to 1.0) |
--reward-account-verification-key-file | Yes | Stake key for the reward account |
--pool-owner-verification-key-file | No | Pool owner stake key (can be repeated) |
--pool-relay-ipv4 | No | Relay IP address with port (e.g., 1.2.3.4:3001) |
--single-host-pool-relay | No | Relay DNS hostname with port (e.g., relay.example.com:3001) |
--multi-host-pool-relay | No | Relay DNS SRV record (e.g., _cardano._tcp.example.com) |
--metadata-url | No | URL to pool metadata JSON |
--metadata-hash | No | Blake2b-256 hash of the metadata file (hex) |
--testnet | No | Use testnet network ID for the reward account |
--out-file | Yes | Output path for the certificate |
metadata-hash
Compute the Blake2b-256 hash of a pool metadata file:
dugite-cli stake-pool metadata-hash \
--pool-metadata-file pool-metadata.json
Output:
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
This hash is required when registering a pool. The metadata file must be served at the URL specified in the registration certificate and the hash must match. The file contents at that URL are checked by other nodes during pool discovery.
Example pool metadata file:
{
"name": "Sandstone Pool",
"description": "A Cardano stake pool running Dugite",
"ticker": "SAND",
"homepage": "https://sandstone.io"
}
retirement-certificate
Create a stake pool retirement certificate:
dugite-cli stake-pool retirement-certificate \
--cold-verification-key-file cold.vkey \
--epoch 500 \
--out-file pool-retire.cert
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Path to the cold verification key |
--epoch | Yes | Epoch at which the pool retires |
--out-file | Yes | Output path for the certificate |
Complete Pool Registration Workflow
# 1. Generate all keys
dugite-cli stake-pool key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-counter-file opcert.counter
dugite-cli stake-pool vrf-key-gen \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
dugite-cli stake-pool kes-key-gen \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
# 2. Issue operational certificate
dugite-cli stake-pool issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-counter-file opcert.counter \
--kes-period 400 \
--out-file opcert.cert
# 3. Create registration certificate
dugite-cli stake-pool registration-certificate \
--cold-verification-key-file cold.vkey \
--vrf-verification-key-file vrf.vkey \
--pledge 500000000 \
--cost 340000000 \
--margin 0.02 \
--reward-account-verification-key-file stake.vkey \
--pool-owner-verification-key-file stake.vkey \
--single-host-pool-relay "relay.example.com:3001" \
--metadata-url "https://example.com/pool.json" \
--metadata-hash "a1b2c3..." \
--out-file pool-reg.cert
# 4. Submit registration in a transaction
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--certificate-file pool-reg.cert \
--out-file tx.body
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--signing-key-file cold.skey \
--signing-key-file stake.skey \
--out-file tx.signed
dugite-cli transaction submit \
--tx-file tx.signed \
--socket-path ./node.sock
Node Commands
The dugite-cli node subcommands manage cold keys, KES keys, VRF keys, and operational certificates for block producer setup.
key-gen
Generate a cold key pair and an operational certificate issue counter:
dugite-cli node key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Output path for the cold verification key |
--cold-signing-key-file | Yes | Output path for the cold signing key |
--operational-certificate-issue-counter-file | Yes | Output path for the opcert issue counter. Canonical cardano-cli spelling; --operational-certificate-issue-counter and the legacy --operational-certificate-counter-file are accepted as aliases |
--key-output-bech32 | No | Write keys as bech32 instead of a text envelope (cardano-cli), instead of --key-output-text-envelope |
--key-output-text-envelope | No | Write keys as a text envelope (default; cardano-cli) |
--key-output-format | No | Deprecated cardano-cli spelling: text-envelope or bech32. Superseded by the two flags above but still accepted |
The cold key identifies your stake pool. Keep the signing key offline (air-gapped) after initial setup.
key-gen-KES
Generate a KES (Key Evolving Signature) key pair. The canonical cardano-cli
subcommand casing is key-gen-KES (cardano-cli itself rejects the lowercase
form); dugite also accepts key-gen-kes as a backward-compatible alias.
dugite-cli node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | Yes | Output path for the KES verification key |
--signing-key-file | Yes | Output path for the KES signing key |
--key-output-bech32 | No | Write keys as bech32 instead of a text envelope |
--key-output-text-envelope | No | Write keys as a text envelope (default) |
--key-output-format | No | Deprecated: text-envelope or bech32 |
KES keys are rotated periodically. Each key is valid for a limited number of KES periods (62 periods on mainnet, approximately 90 days total).
key-gen-VRF
Generate a VRF (Verifiable Random Function) key pair. Canonical casing is
key-gen-VRF; key-gen-vrf is accepted as an alias.
dugite-cli node key-gen-VRF \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | Yes | Output path for the VRF verification key |
--signing-key-file | Yes | Output path for the VRF signing key |
--key-output-bech32 | No | Write keys as bech32 instead of a text envelope |
--key-output-text-envelope | No | Write keys as a text envelope (default) |
--key-output-format | No | Deprecated: text-envelope or bech32 |
VRF keys are used for slot leader election and do not need rotation.
key-hash-VRF
Get the Blake2b-256 hash of a VRF verification key. Canonical casing is
key-hash-VRF; key-hash-vrf is accepted as an alias.
dugite-cli node key-hash-VRF \
--verification-key-file vrf.vkey
| Flag | Required | Description |
|---|---|---|
--verification-key-file | One of these two | Path to the VRF verification key file |
--verification-key | One of these two | VRF verification key as an inline bech32 or hex STRING (cardano-cli alternative to the -file form) |
--out-file | No | Write the hash to a file instead of stdout |
Note this hash uses Blake2b-256 (32 bytes) — a different convention from
key verification-key-hash, which is Blake2b-224 (28 bytes) and rejects
VRF/KES key types outright.
issue-op-cert
Issue an operational certificate binding the cold key to the current KES key:
dugite-cli node issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter \
--kes-period 400 \
--out-file opcert.cert
| Flag | Required | Description |
|---|---|---|
--kes-verification-key-file | Yes | Path to the KES verification key |
--cold-signing-key-file | Yes | Path to the cold signing key |
--operational-certificate-issue-counter-file | Yes | Path to the opcert issue counter (incremented automatically). --operational-certificate-issue-counter and --operational-certificate-counter-file are accepted as aliases |
--kes-period | Yes | Current KES period (current_slot / slots_per_kes_period) |
--out-file | Yes | Output path for the operational certificate |
The opcert must be regenerated each time you rotate KES keys. The counter file is incremented each time to prevent replay attacks.
new-counter
Create a new operational certificate issue counter (useful if the original counter is lost):
dugite-cli node new-counter \
--cold-verification-key-file cold.vkey \
--counter-value 5 \
--operational-certificate-issue-counter-file opcert.counter
| Flag | Required | Description |
|---|---|---|
--cold-verification-key-file | Yes | Path to the cold verification key |
--counter-value | Yes | Counter value to set |
--operational-certificate-issue-counter-file | Yes | Output path for the counter file. --operational-certificate-issue-counter and --operational-certificate-counter-file are accepted as aliases |
Governance
Dugite CLI supports Conway-era governance operations as defined in CIP-1694. This includes DRep management, voting, and governance action creation.
DRep Operations
Generate DRep Keys
dugite-cli governance drep key-gen \
--signing-key-file drep.skey \
--verification-key-file drep.vkey
Get DRep ID
# Bech32 format (default)
dugite-cli governance drep id \
--drep-verification-key-file drep.vkey
# Hex format
dugite-cli governance drep id \
--drep-verification-key-file drep.vkey \
--output-format hex
DRep Registration
Create a DRep registration certificate:
dugite-cli governance drep registration-certificate \
--drep-verification-key-file drep.vkey \
--key-reg-deposit-amt 500000000 \
--anchor-url "https://example.com/drep-metadata.json" \
--anchor-data-hash "a1b2c3d4..." \
--out-file drep-reg.cert
The --key-reg-deposit-amt should match the current DRep deposit parameter (currently 500 ADA = 500000000 lovelace on mainnet).
DRep Retirement
dugite-cli governance drep retirement-certificate \
--drep-verification-key-file drep.vkey \
--deposit-amt 500000000 \
--out-file drep-retire.cert
DRep Update
Update DRep metadata:
dugite-cli governance drep update-certificate \
--drep-verification-key-file drep.vkey \
--anchor-url "https://example.com/drep-metadata-v2.json" \
--anchor-data-hash "d4e5f6a7..." \
--out-file drep-update.cert
Voting
Create a Vote
Votes can be cast by DReps, SPOs, or Constitutional Committee members:
DRep vote:
dugite-cli governance vote create \
--governance-action-tx-id "a1b2c3d4..." \
--governance-action-index 0 \
--vote yes \
--drep-verification-key-file drep.vkey \
--out-file vote.json
SPO vote:
dugite-cli governance vote create \
--governance-action-tx-id "a1b2c3d4..." \
--governance-action-index 0 \
--vote no \
--cold-verification-key-file cold.vkey \
--out-file vote.json
Constitutional Committee vote:
dugite-cli governance vote create \
--governance-action-tx-id "a1b2c3d4..." \
--governance-action-index 0 \
--vote yes \
--cc-hot-verification-key-file cc-hot.vkey \
--out-file vote.json
Vote Values
| Value | Description |
|---|---|
yes | Vote in favor |
no | Vote against |
abstain | Abstain from voting |
Vote with Anchor
Attach rationale metadata to a vote:
dugite-cli governance vote create \
--governance-action-tx-id "a1b2c3d4..." \
--governance-action-index 0 \
--vote yes \
--drep-verification-key-file drep.vkey \
--anchor-url "https://example.com/vote-rationale.json" \
--anchor-data-hash "e5f6a7b8..." \
--out-file vote.json
Governance Actions
Info Action
A governance action that carries no on-chain effect (used for signaling):
dugite-cli governance action create-info \
--anchor-url "https://example.com/proposal.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--out-file info-action.json
No Confidence Motion
Express no confidence in the current constitutional committee:
dugite-cli governance action create-no-confidence \
--anchor-url "https://example.com/no-confidence.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--prev-governance-action-tx-id "d4e5f6a7..." \
--prev-governance-action-index 0 \
--out-file no-confidence.json
New Constitution
Propose a new constitution:
dugite-cli governance action create-constitution \
--anchor-url "https://example.com/constitution-proposal.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--constitution-url "https://example.com/constitution.txt" \
--constitution-hash "e5f6a7b8..." \
--constitution-script-hash "b8c9d0e1..." \
--out-file new-constitution.json
Hard Fork Initiation
Propose a protocol version change:
dugite-cli governance action create-hard-fork-initiation \
--anchor-url "https://example.com/hardfork.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--protocol-major-version 10 \
--protocol-minor-version 0 \
--out-file hardfork.json
Protocol Parameters Update
Propose changes to protocol parameters:
dugite-cli governance action create-protocol-parameters-update \
--anchor-url "https://example.com/pp-update.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--protocol-parameters-update pp-changes.json \
--prev-governance-action-tx-id "d4e5f6a7..." \
--prev-governance-action-index 0 \
--constitution-script-hash "b8c9d0e1..." \
--out-file pp-update.json
--prev-governance-action-tx-id/--prev-governance-action-index (the
enacted-action chain pointer) and --constitution-script-hash (the optional
guardrail script hash) may be omitted.
The pp-changes.json file contains the parameter fields to change:
{
"txFeePerByte": 44,
"txFeeFixed": 155381,
"maxBlockBodySize": 90112,
"maxTxSize": 16384
}
Update Committee
Propose changes to the constitutional committee:
dugite-cli governance action create-update-committee \
--anchor-url "https://example.com/committee-update.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--remove-cc-cold-verification-key-hash "old_member_hash" \
--add-cc-cold-verification-key-hash "new_member_hash,500" \
--threshold "2/3" \
--out-file committee-update.json
The --add-cc-cold-verification-key-hash uses the format key_hash,expiry_epoch.
Treasury Withdrawal
Propose a withdrawal from the treasury:
dugite-cli governance action create-treasury-withdrawal \
--anchor-url "https://example.com/withdrawal.json" \
--anchor-data-hash "a1b2c3d4..." \
--deposit 100000000000 \
--return-addr "addr_test1qz..." \
--funds-receiving-stake-verification-key-file recipient.vkey \
--transfer 50000000000 \
--out-file treasury-withdrawal.json
Hash Anchor Data
Compute the Blake2b-256 hash of an anchor data file:
# Binary file
dugite-cli governance action hash-anchor-data \
--file-binary proposal.json
# Text file
dugite-cli governance action hash-anchor-data \
--file-text proposal.txt
Submitting Governance Actions
Governance actions and votes are submitted as part of transactions. Include the certificate or vote file when building the transaction:
# Submit a DRep registration
dugite-cli transaction build \
--tx-in "abc123...#0" \
--tx-out "addr_test1qz...+5000000" \
--change-address "addr_test1qp..." \
--fee 200000 \
--certificate-file drep-reg.cert \
--out-file tx.body
dugite-cli transaction sign \
--tx-body-file tx.body \
--signing-key-file payment.skey \
--signing-key-file drep.skey \
--out-file tx.signed
dugite-cli transaction submit \
--tx-file tx.signed \
--socket-path ./node.sock
Architecture Overview
Dugite is organized as a 16-crate Cargo workspace under crates/ (plus an xtask build-tooling
crate and two test-only crates, tests/conformance and tests/golden, outside crates/). Each
crate has a focused responsibility and well-defined dependencies.
Crate Workspace
| Crate | Description |
|---|---|
dugite-primitives | Core types: hashes, blocks, transactions, addresses, values, protocol parameters (Byron through Conway, plus the in-progress Dijkstra era) |
dugite-crypto | Ed25519 keys, VRF, KES, text envelope format |
dugite-serialization | In-house multi-era CBOR encoding/decoding for Cardano wire format |
dugite-lsm | Pure Rust LSM-tree engine with WAL, compaction, bloom filters, and snapshots — standalone, no dependency on any other workspace crate |
dugite-network | Ouroboros mini-protocols (ChainSync, BlockFetch, TxSubmission, KeepAlive, PeerSharing), N2N client/server, N2C server, peer manager |
dugite-consensus | Ouroboros Praos, chain selection, epoch transitions, slot leader checks |
dugite-ledger | UTxO set (LSM-backed via UTxO-HD), transaction validation, ledger state, certificate processing, native script evaluation, reward calculation |
dugite-mempool | Thread-safe transaction mempool with input-conflict checking and TTL sweep (depends on dugite-ledger for validation types) |
dugite-storage | ChainDB (ImmutableDB append-only chunk files + VolatileDB in-memory) |
dugite-node | Main binary, config, topology, pipelined chain sync loop, Mithril import, block forging |
dugite-rpc | Native UTxO RPC (gRPC) server exposing chain/mempool data via the utxorpc spec |
dugite-cli | cardano-cli compatible CLI (address, key, transaction, query, stake-address, stake-pool, governance, node, genesis, byron, and text-view command groups) |
dugite-monitor | Terminal monitoring dashboard (ratatui-based, real-time metrics via Prometheus polling) — standalone binary with no internal crate dependencies |
dugite-config | Interactive TUI configuration editor with tree navigation, inline editing, type validation, and diff view (depends on dugite-node for the config/runtime types it edits) |
dugite-uplc | In-house UPLC CEK machine for Plutus V1/V2/V3 (and Dijkstra's V4) script evaluation |
dugite-integration-tests | End-to-end integration tests across the workspace |
Crate Dependency Graph
graph TD
NODE[dugite-node] --> NET[dugite-network]
NODE --> CONS[dugite-consensus]
NODE --> LEDGER[dugite-ledger]
NODE --> STORE[dugite-storage]
NODE --> POOL[dugite-mempool]
NODE --> UPLC[dugite-uplc]
NODE --> RPC[dugite-rpc]
CLI[dugite-cli] --> NET
CLI --> CONS
CLI --> PRIM[dugite-primitives]
CLI --> CRYPTO[dugite-crypto]
CLI --> SER[dugite-serialization]
CFG[dugite-config] --> NODE
NET --> PRIM
NET --> CRYPTO
NET --> SER
NET --> CONS
CONS --> PRIM
CONS --> CRYPTO
CONS --> SER
LEDGER --> PRIM
LEDGER --> CRYPTO
LEDGER --> SER
LEDGER --> LSM[dugite-lsm]
LEDGER --> UPLC
STORE --> PRIM
STORE --> SER
STORE --> CRYPTO
STORE --> CONS
POOL --> PRIM
POOL --> LEDGER
POOL --> CRYPTO
RPC --> PRIM
RPC --> POOL
RPC --> SER
UPLC --> PRIM
UPLC --> SER
SER --> PRIM
CRYPTO --> PRIM
Notably, dugite-mempool depends on dugite-ledger (it reuses the Phase-1/Phase-2 validation
types), not the reverse — the mempool is a thin admission-control layer over the ledger's own
validation, not an independent crate the ledger reaches into. dugite-monitor and dugite-lsm
are the two workspace leaves with zero dependencies on other Dugite crates: the monitor talks to
a running node purely over Prometheus HTTP and the N2C socket, and the LSM engine is a
general-purpose on-disk data structure with no Cardano-specific knowledge.
Key Dependencies
- tokio — Async runtime
- dugite-lsm — Pure Rust LSM tree for the on-disk UTxO set (UTxO-HD)
- minicbor — CBOR encoding for custom types
- ed25519-dalek — Ed25519 signatures
- blake2b_simd — SIMD-accelerated Blake2b hashing
- clap — CLI argument parsing
- tracing — Structured logging
Design Principles
Zero-Warning Policy
All code must compile with RUSTFLAGS="-D warnings" and pass cargo clippy --all-targets -- -D warnings. This is enforced by CI.
Wire-Format Compatibility
Dugite uses an in-house multi-era CBOR decoder (dugite-serialization) for all block and transaction deserialization, ensuring exact wire-format compatibility with cardano-node. Internal types (dugite-primitives) are populated directly from the decoded CBOR.
Key patterns:
Transaction.hashisblake2b_256(raw_body_cbor)over bytes captured byKeepRaw::parse_withduring decodeChainSyncEvent::RollForwardusesBox<Block>to avoid large enum variant size- Invalid transactions (
is_valid: false) are skipped duringapply_block - Pool IDs are
Hash28(Blake2b-224), notHash32
Multi-Era Support
Dugite handles all Cardano eras from Byron through Conway, plus early support for the not-yet-released Dijkstra era (protocol version 12, storage era tag 8, HFC index 7 — includes PlutusV4). The serialization layer handles era-specific block formats transparently, while the ledger layer applies era-appropriate validation rules.
Sync Pipeline
Dugite pipelines block synchronization, separating header collection (one ChainSync task per hot peer) from block body fetching (deliberately serialized onto a single "best" peer at a time) for maximum throughput without wasting bandwidth on duplicate downloads.
Architecture
flowchart LR
subgraph "Hot Peers (ChainSync, per-peer tasks)"
CS1[Peer 1<br/>ChainSync]
CS2[Peer 2<br/>ChainSync]
CS3[Peer N<br/>ChainSync]
end
CS1 -->|headers| CC[Candidate Chains<br/>per-peer state]
CS2 -->|headers| CC
CS3 -->|headers| CC
CC -->|GSV bandwidth<br/>preference| SLOT{{Single Active<br/>Fetcher Slot}}
SLOT -->|MsgRequestRange| BF[BlockFetch Worker<br/>current best peer]
BF -->|FetchedBlock| CHAN[[mpsc channel<br/>cap 4096]]
CHAN --> BP[Block Processor<br/>apply_fetched_block]
BP --> CDB[(ChainDB)]
BP --> LS[Ledger State]
Pipeline Stages
1. Header Collection (ChainSync, per hot peer)
Every hot peer runs its own ChainSync client task using the N2N ChainSync mini-protocol (V14+).
Each task pipelines up to DUGITE_PIPELINE_DEPTH (default 300) MsgRequestNext messages
in flight rather than waiting for each MsgRollForward serially, and writes its results into a
per-peer CandidateChainState entry shared with the BlockFetch decision loop.
The ChainSync protocol involves:
- MsgFindIntersect — Find a common point between the node and the peer
- MsgRequestNext — Request the next header
- MsgRollForward — Receive a new header
- MsgRollBackward — Handle a chain reorganization
2. Block Fetch — single active fetcher, GSV-preferred
Header collection is per-peer and concurrent, but block body downloading deliberately is not:
only one BlockFetch worker is allowed to hold the "active fetcher" slot at a time, matching
Haskell's bfcMaxConcurrencyBulkSync = 1. This was a validated finding, not an oversight —
concurrent multi-peer body fetching was measured to be slower in practice (duplicate/wasted
downloads and lock contention outweigh the extra bandwidth), so dugite concentrates fetching on
whichever peer is currently serving fastest.
Peer workers contend for the slot via a lock-free compare_exchange on a shared atomic, polled
every 10ms (matching Haskell's bfcDecisionLoopIntervalPraos). When the slot is free, only the
top K=2 peers ranked by measured fetch bandwidth (an EWMA of bytes/sec per completed range,
tracked per peer as "GSV" / "fetchyness" in PeerManager) are allowed to claim it — a hot standby
so a momentarily-busy best peer can't stall the slot, while fetching still concentrates on the
fastest peers rather than round-robining fairly.
The BlockFetch protocol involves:
- MsgRequestRange — Request a range of blocks by header hash
- MsgBlock — Receive a block
- MsgBatchDone — Signal the end of a batch
Each range's size is chosen adaptively against an 8 MiB byte budget (BLOCKFETCH_RANGE_BYTE_BUDGET)
using a running average of recently-seen block sizes, clamped to [64, MAX_BLOCKS_PER_FETCH]
blocks (MAX_BLOCKS_PER_FETCH = 2000, the network's per-batch DoS cap; operator-overridable up to
that ceiling via DUGITE_BLOCKFETCH_MAX_RANGE). This auto-grows toward the cap for tiny Byron
blocks and shrinks for large Conway blocks, so the worker's per-range decode buffer stays bounded
in every era. Up to 2 MsgRequestRange requests are pipelined in flight at once
(BLOCKFETCH_PIPELINE_WINDOW) so the next range's network round-trip overlaps the previous
range's receipt/decode instead of being paid serially.
3. Block Processing
Each FetchedBlock arrives over an mpsc channel (capacity 4096, overridable via
DUGITE_FETCHED_BLOCKS_CAP) and is applied to the ledger state as it is dequeued:
- Deserialization — Raw CBOR bytes are decoded into Dugite's internal
Blocktype using Dugite's in-house multi-era CBOR decoder.Transaction.hashis computed asblake2b_256over the original wire bytes captured during decode (KeepRaw::parse_with), never a re-encoding — a load-bearing invariant, since a re-encode that differs from the wire bytes by even one byte would silently diverge the hash from Haskell's. - Ledger validation — Each block is validated against the current ledger state (UTxO checks, fee validation, certificate processing)
- Storage — Valid blocks are added to the ChainDB (volatile database first, flushed to immutable when k-deep) — the ChainDB write happens before the ledger apply, so a crash mid-apply never leaves the ledger ahead of durable storage
- Epoch transitions — At epoch boundaries, stake snapshots are rotated and rewards are calculated
Progress Reporting
Progress is logged periodically, showing:
- Current slot and block number
- Epoch number
- UTxO count
- Sync percentage (based on slot vs. wall-clock time)
- Blocks-per-second throughput metric
Rollback Handling
When the ChainSync peer sends a MsgRollBackward message, the node:
- Identifies the rollback point (a slot/hash pair)
- Removes rolled-back blocks from the VolatileDB
- Reverts the ledger state to the rollback point
- Resumes header collection from the new tip
Only blocks in the VolatileDB (the last k=2160 blocks) can be rolled back. Blocks that have been flushed to the ImmutableDB are permanent.
Pipelined ChainSync
Dugite uses pipelined ChainSync to avoid the round-trip latency bottleneck of serial header requests. Instead of waiting for each MsgRollForward before requesting the next header, the node sends up to 300 MsgRequestNext messages concurrently (configurable via DUGITE_PIPELINE_DEPTH).
This bypasses a serial ChainSync state machine in favor of a custom implementation that manages the pipeline depth directly.
Performance Characteristics
- Header collection is pipelined per peer (up to 300 in-flight requests, configurable via
DUGITE_PIPELINE_DEPTH) and runs concurrently across every hot peer - Block body fetching is deliberately single-peer at any instant (GSV-preferred, top-
K=2hot standby) — measured faster in practice than concurrent multi-peer body fetching, which wasted bandwidth on duplicate/contended downloads - Block processing applies blocks one at a time as they are dequeued from the fetch channel, in slot order
- Throughput depends on network latency, the current fetch peer's bandwidth, and block sizes — the sustained ceiling is set by whichever is slower: peer download bandwidth or ledger-apply throughput
On preview testnet, full sync from genesis completes in approximately 10 hours, with block replay (from Mithril snapshot) achieving ~13,700 blocks/second.
Storage
Dugite's storage layer is implemented in the dugite-storage and dugite-ledger crates. It closely mirrors the cardano-node architecture with three distinct storage subsystems coordinated by ChainDB.
Storage Architecture
flowchart TD
CDB[ChainDB] --> VOL[VolatileDB<br/>In-Memory HashMap<br/>Last k=2160 blocks]
CDB --> IMM[ImmutableDB<br/>Append-Only Chunk Files<br/>Finalized blocks]
NEW[New Block] -->|add_block| VOL
VOL -->|flush when > k blocks| IMM
READ[Block Query] -->|1. check volatile| VOL
READ -->|2. fallback to immutable| IMM
ROLL[Rollback] -->|remove from volatile| VOL
LS[LedgerState] --> UTXO[UtxoStore<br/>dugite-lsm LSM tree<br/>On-disk UTxO set]
LS --> DIFF[DiffSeq<br/>Last k UTxO diffs<br/>For rollback]
Block Storage
ImmutableDB (Append-Only Chunk Files)
The ImmutableDB stores finalized blocks in append-only chunk files on disk. This matches cardano-node's ImmutableDB design — blocks are simply appended to files and are inherently durable without any snapshot mechanism.
Properties:
- Always durable — append-only writes survive process crashes without special persistence logic
- No LSM tree — plain chunk files, no compaction or memtable overhead
- Sequential access — optimized for the append-heavy, read-sequential block storage workload
- Secondary indexes — slot-to-offset and hash-to-slot mappings for efficient lookups
- Memory-mapped block index — on-disk open-addressing hash table (
hash_index.dat) provides 3-5x faster lookups than in-memory HashMap while using near-zero RSS
Crash Recovery & Integrity
A 2026-07-28 preprod incident (a hard stop that lost the active chunk's secondary index and wedged sync for every peer) drove a durability hardening pass across the ImmutableDB, shipped in v2.4.0 (#926-#929):
- Per-append secondary-index writes — each block's secondary-index entry is written to disk
as it is appended (
active.secondary_file.write_all(...)per block), not buffered in memory until a clean shutdown. Previously a hard stop lost every index entry written since the node started, even though the block data itself was already durable. - Open-time reconciliation in both open paths —
ImmutableDB::open()(read-only) andopen_for_writing()both runreconcile_chunks_on_disk()before any other file is touched. Older versions only validated on the read-only path, so the node's own write-mode startup never checked for damage. - Tail-chunk CRC verification and truncation — every block in the highest-numbered
("tail") chunk is CRC32-verified; a chunk's true recoverable end is recovered by scanning for
the last CRC-matching
0x82-envelope boundary, and the file is truncated to that verified prefix. Damage strictly below the tail is refused with a hardInconsistentChunkerror rather than silently repaired. .chunk.orphanedquarantine — a non-empty tail chunk with no matching secondary index is renamed to<num>.chunk.orphanedand excluded from the chain, preserved on disk for manual inspection instead of being silently skipped or overwritten.- Cross-chunk boundary linkage — adjacent chunks are checked so the first block of a chunk
correctly chains onto the previous chunk's tip (
prev_hash), the dugite equivalent of Haskell'sChunkFileDoesntFit. Per-chunk CRC checks alone are not sufficient to catch this — an internally-valid orphan island can still pass every per-chunk check while being disconnected from the canonical chain. tip.metaclamping — the cached tip metadata is only trusted when it matches the last indexed entry's(slot, hash); otherwise it is clamped to the true indexed tip (recovering the correct block number by decoding the tip block) and rewritten.immutable/cleanmarker — a zero-bytecleanmarker file is written by the shutdown flush and removed the moment the DB re-enters write mode. Its presence gates whether the memory-mappedhash_index.datcan be reused as-is; its absence (an unclean stop) forces a rebuild, since mmap pages may have reached disk in an order that leaves stale offsets behind.- Exclusive directory lock —
ChainDB::opentakes an advisoryflock(2)on<database-path>/lockbefore touching any other file (the dugite equivalent of Haskell'swithLockDB). A second process opening the same--database-pathfails fast, naming the holder's pid, instead of both processes silently interleaving writes into the same chunk files.
Operational consequence: always stop dugite-node with SIGTERM, never SIGKILL. A graceful
stop runs the shutdown flush (writes the clean marker, fsyncs the tail chunk's index); a killed
process leaves clean absent and relies entirely on the open-time reconciliation above to recover
— safe, but strictly more expensive and unnecessary to trigger routinely.
VolatileDB (In-Memory HashMap)
The VolatileDB stores recent blocks (the last k=2160 blocks) in an in-memory HashMap. This enables:
- Fast reads — no disk I/O for recent blocks
- Efficient rollback — blocks can be removed without touching disk
- Simple eviction — when a block becomes k-deep, it is flushed to the ImmutableDB
The VolatileDB has no on-disk representation — it exists only in memory and is rebuilt from the ImmutableDB tip on restart.
ChainDB
ChainDB is the unified interface for block storage. It coordinates the ImmutableDB and VolatileDB:
- New blocks arrive from peers and are added to the VolatileDB
- Once a block is more than k slots deep (k=2160 for mainnet), it is flushed from the VolatileDB to the ImmutableDB
- Flushed blocks are removed from the VolatileDB
The ChainDB write for a new block always happens before that block is applied to the ledger state — never the reverse. If the node crashes between the two steps, the block is durably stored but not yet reflected in the ledger, so recovery simply re-applies it; the opposite ordering could leave the ledger ahead of durable storage with no way to recover the block that produced that state.
When querying for a block:
- The VolatileDB is checked first (fast, in-memory)
- If not found, the ImmutableDB is consulted (disk-based)
Block Range Queries
ChainDB supports querying blocks by slot range:
- VolatileDB scans its HashMap for matching slots
- ImmutableDB uses secondary indexes for slot range scanning
- Results from both databases are merged
UTxO Storage (UTxO-HD)
The UTxO set is stored on disk using dugite-lsm, a pure Rust LSM tree. This matches Haskell cardano-node's UTxO-HD architecture, where the UTxO set lives in an LSM-backed on-disk store rather than entirely in memory.
UtxoStore
The UtxoStore (in dugite-ledger) wraps a dugite-lsm LsmTree and provides:
- Disk-backed UTxO set — the full UTxO set lives on disk, not in memory
- Efficient point lookups — bloom filters for fast negative lookups
- Batch writes — UTxO inserts and deletes are batched per block
- Snapshots — periodic snapshots for crash recovery
dugite-lsm is configured via storage profiles that maximize available system memory:
| Profile | Target System | Memtable | Block Cache | Expected RSS |
|---|---|---|---|---|
ultra-memory | 32GB | 2GB | 24GB | ~27GB |
high-memory (default) | 16GB | 1GB | 12GB | ~14GB |
low-memory | 8GB | 512MB | 5GB | ~6.5GB |
minimal | 4GB | 256MB | 2GB | ~3GB |
All profiles use 10 bits per key bloom filters and hybrid compaction (tiered L0, leveled L1+).
DiffSeq (Rollback Support)
The DiffSeq (in dugite-ledger) maintains the last k blocks of UTxO diffs, enabling rollback without replaying blocks:
- Each block produces a
UtxoDiffrecording which UTxOs were added and removed - The
DiffSeqholds the last k=2160 diffs - On rollback, diffs are applied in reverse to restore the UTxO set
io_uring Support (Linux)
On Linux with kernel 5.1+, enable io_uring for async I/O in the UTxO LSM tree:
cargo build --release --features io-uring
On other platforms (macOS, Windows), the feature flag is accepted but falls back to synchronous I/O automatically.
Snapshot Policy
Dugite uses a time-based snapshot policy matching Haskell's cardano-node:
- Normal sync: snapshots every 72 minutes (k * 2 seconds, where k=2160)
- Bulk sync: snapshots every 50,000 blocks plus 6 minutes of wall-clock time
- Maximum retained: 2 snapshots on disk at any time
Ledger snapshots include the full ledger state (stake distribution, protocol parameters, governance state, etc.). The UTxO set is persisted separately via the UtxoStore's LSM snapshots.
Tip Recovery
When the node restarts:
- The ImmutableDB tip is read from the chunk files (always durable)
- The VolatileDB starts empty (in-memory state is rebuilt)
- The ledger state is restored from the most recent snapshot
- The UTxO set is restored from the UtxoStore's LSM snapshot
- The node resumes syncing from the recovered tip
Disk Layout
database-path/
lock # Advisory flock held for the ChainDB's lifetime (#929)
immutable/ # ImmutableDB — chunk and index files live flat, not nested
00000.chunk # Block data, one file per chunk
00000.secondary # Per-chunk secondary index (slot/hash -> offset), written per block append
00001.chunk
00001.secondary
...
tip.meta # Cached (slot, hash, block_no) tip — clamped to the indexed chain if stale
clean # Zero-byte marker written on graceful shutdown; absent after a hard stop
hash_index.dat # Mmap block index (open-addressing hash table)
utxo-store/ # dugite-lsm database (UTxO set)
active/ # Current SSTables
snapshots/ # Durable snapshots
ledger/ # Ledger state snapshots
Performance Considerations
- Block writes — append-only chunk files provide consistent write performance without compaction pauses
- UTxO lookups — LSM tree with bloom filters provides efficient point lookups for transaction validation
- Memory usage — the VolatileDB holds approximately k blocks in memory (typically a few hundred MB). The UTxO set lives on disk, significantly reducing memory pressure compared to an all-in-memory approach
- Batch size — the flush batch size balances memory usage against write efficiency
Storage Profiles
Dugite provides four storage profiles sized to maximize available system memory:
# Select a profile via CLI
./dugite-node run --storage-profile high-memory ...
# Override individual parameters
./dugite-node run --storage-profile low-memory --utxo-block-cache-size-mb 4096 ...
Profiles can also be set in the node configuration file:
{
"storage": {
"profile": "high-memory",
"utxoBlockCacheSizeMb": 8192
}
}
Resolution order: profile defaults < config file overrides < CLI overrides.
Fork Recovery & ImmutableDB Contamination
Problem
When a forged block loses a slot battle, flush_all_to_immutable on graceful shutdown can persist orphaned blocks permanently in the ImmutableDB. Since the ImmutableDB is append-only and designed for finalized blocks, these orphaned blocks contaminate the canonical chain history and can cause intersection failures on reconnect.
sequenceDiagram
participant Node as Dugite Node
participant Vol as VolatileDB
participant Imm as ImmutableDB
participant Peer as Upstream Peer
Node->>Vol: Forge block at slot S
Peer->>Node: Competing block at slot S wins
Note over Vol: Orphaned forged block still in VolatileDB
Node->>Imm: flush_all_to_immutable (graceful shutdown)
Note over Imm: Orphaned block now persisted permanently
Node->>Peer: Restart — intersection negotiation fails
Detection
ChainDB.get_chain_points()walks backwards through volatile blocks viaprev_hashlinks, providing the peer with enough ancestry for intersection even when the tip is orphaned.ImmutableDB.get_historical_points()samples older chunk secondary indexes in reverse order, providing canonical intersection points even when the immutable tip is contaminated.- When fork divergence is detected, contaminated ChainDB chain points are excluded from intersection negotiation, preventing the node from advertising orphaned blocks to peers.
Recovery
- Case A (Origin intersection): The volatile DB is cleared, the ledger state is reset, and the node reconnects from genesis. This is the fallback when no valid intersection can be found.
- Case B (Intersection behind ledger): A targeted ImmutableDB replay is performed up to the intersection slot using a detached LSM store, achieving approximately 50K blocks/second replay speed. This avoids a full resync while restoring the ledger to a consistent state.
Benchmarks
Run storage benchmarks with:
# Storage benchmarks (block index, ImmutableDB, ChainDB, scaling to 1M entries)
cargo bench -p dugite-storage --bench storage_bench
# UTxO store benchmarks (insert, lookup, apply_tx, LSM configs, scaling to 1M entries)
cargo bench -p dugite-ledger --bench ledger_bench
# Crypto benchmarks (Ed25519, blake2b keyhash)
cargo bench -p dugite-crypto --bench crypto_bench
# Hash benchmarks (blake2b_256, blake2b_224, batch hashing)
cargo bench -p dugite-primitives --bench primitives_bench
Results are saved to target/criterion/ with HTML reports. Baseline results are tracked in benches/.
Latest Results (Apple M2 Max, 32GB, 2026-03-14)
Block Index Lookup (500 random lookups, mmap vs in-memory HashMap)
| Size | In-Memory | Mmap | Speedup |
|---|---|---|---|
| 10K | 10.0µs | 2.83µs | 3.5x |
| 100K | 10.1µs | 2.17µs | 4.7x |
| 1M | 10.6µs | 2.01µs | 5.3x |
Mmap lookup advantage grows with scale — at mainnet block counts (~10M), the gap widens further.
UTxO Store Scaling (dugite-lsm LSM tree)
| Size | Insert (per-entry) | Lookup (per-entry) | Total Lovelace Scan |
|---|---|---|---|
| 10K | 455ns | 191ns | 2.38ms |
| 100K | 479ns | 236ns | 29.1ms |
| 1M | 569ns | 308ns | 330ms |
Insert and lookup scale near-linearly. At mainnet scale (~20M UTxOs), estimated full scan ~6.6s.
Crypto & Hashing
| Operation | Time |
|---|---|
| Ed25519 verify (single) | 28.6µs |
| Blake2b-224 keyhash (32B) | 128ns |
| Blake2b-256 tx hash (1KB) | 949ns |
A typical block with 50 witnesses: ~1.4ms for signature verification, ~6.4µs for keyhash computation.
LSM Config Comparison (100K entries)
All storage profiles perform identically at benchmark scale — config differences emerge at mainnet scale (20M+ UTxOs) where working set exceeds cache capacity.
See benches/2026-03-14-all-profiles.md for full results.
Ledger
Dugite's ledger layer (dugite-ledger) implements full Cardano transaction validation, UTxO management, stake distribution, reward calculation, and Conway-era governance, with early support for the in-progress Dijkstra era. It closely follows the Haskell cardano-ledger STS (State Transition System) rules.
Ledger State
The LedgerState is the complete mutable state of the Cardano ledger at a given point in the chain:
flowchart TD
LS[LedgerState] --> UTXO[UtxoSet<br/>On-disk via LSM tree]
LS --> DELEG[Delegations<br/>Stake → Pool mapping]
LS --> POOLS[Pool Parameters<br/>Registered pools + future updates]
LS --> REWARDS[Reward Accounts<br/>Per-credential balances]
LS --> GOV[GovernanceState<br/>DReps, proposals, committee, constitution]
LS --> SNAP[EpochSnapshots<br/>Mark / Set / Go]
LS --> PP[Protocol Parameters<br/>Current + previous epoch]
LS --> FIN[Treasury + Reserves<br/>Financial state]
Key design decisions:
- Arc-wrapped collections — Large mutable fields (
delegations,pool_params,reward_accounts,governance) are wrapped inArcfor copy-on-write semantics. CloningLedgerStatebumps reference counts; mutations viaArc::make_mut()only copy when shared. - On-disk UTxO — The UTxO set lives in an LSM tree (
dugite-lsm) rather than in memory, matching Haskell's UTxO-HD architecture. At mainnet scale (~20M UTxOs), this avoids multi-gigabyte memory pressure. - Exact rational arithmetic — Reward calculations use
Rat(backed bynum_bigint::BigInt) for lossless intermediate computation, with a single floor operation at the end matching Haskell'srationalToCoinViaFloor.
Block Application Pipeline
When a new block arrives, apply_block() processes it through this pipeline:
flowchart TD
BLK[New Block] --> CONN[Check prev_hash chain]
CONN --> EPOCH{Epoch boundary?}
EPOCH -->|Yes| ET[Process epoch transition]
EPOCH -->|No| TXS[Process transactions]
ET --> TXS
TXS --> P1[Phase-1 Validation<br/>Structural + witness checks]
P1 --> P2{Plutus scripts?}
P2 -->|Yes| EVAL[Phase-2 Evaluation<br/>dugite-uplc CEK machine]
P2 -->|No| APPLY[Apply UTxO changes]
EVAL --> APPLY
APPLY --> CERT[Process certificates]
CERT --> GOV[Process governance actions]
GOV --> DIFF[Record UtxoDiff]
Block Validation Modes
| Mode | Plutus Evaluation | Use Case |
|---|---|---|
ValidateAll | Re-evaluate, verify is_valid flag | New blocks from peers |
ApplyOnly | Trust is_valid flag | ImmutableDB replay, Mithril import, self-forged blocks |
Invalid transactions (is_valid: false) skip normal input/output processing. Instead, collateral inputs are consumed and collateral return is added.
Transaction Validation
Phase-1 (Structural + Witness)
Phase-1 validation checks structural rules without executing scripts:
-
Inputs exist — All transaction inputs are present in the UTxO set
-
Fee sufficient — Fee covers minimum fee based on tx size, execution units, and reference script size (CIP-0112 tiered pricing in Conway)
-
Value conserved — Inputs = outputs + fee (+ minting/burning for multi-asset)
-
TTL valid — Transaction has not expired (time-to-live check against current slot)
-
Witness verification — Ed25519 signatures match required signers from inputs, withdrawals, and certificates
-
Multi-asset rules — No negative quantities, minting requires policy witness
-
Reference inputs — All reference inputs exist (not consumed, only read)
-
Output minimum — Each output meets the minimum lovelace requirement, computed by
ProtocolParameters::min_coin_for_output(), which dispatches by the era's protocol version rather than applying one formula everywhere (Haskell'sgetMinCoinTxOutis likewise defined per era):- PV 0-3 (Shelley, Allegra) — flat
minUTxOValueprotocol parameter, independent of the output's contents - PV 4 (Mary) — ada-only outputs use the same flat
minUTxOValue; multi-asset outputs use Mary'sscaledMinDeposit, scalingminUTxOValue / 27by(27 + value_size) - PV 5-6 (Alonzo) —
utxoEntrySize * coinsPerUTxOWord, whereutxoEntrySizeis27 + mary_value_size + (10 if a datum hash is present else 0) - PV >= 7 (Babbage, Conway, Dijkstra) — the serialized-output-size formula,
(160 + size) * coinsPerUTxOByte, using the output's actual encoded byte size (from the original wire CBOR when available, or dugite's own re-encoding otherwise)
This per-era split matters in practice:
ada_per_utxo_byteis seeded from the Alonzo genesis file at node startup regardless of which era the chain is currently in, so applying the Babbage/Conway formula unconditionally would falsely reject real Shelley/Allegra/Mary mainnet transactions with small outputs. - PV 0-3 (Shelley, Allegra) — flat
-
Transaction size — Does not exceed max transaction size
-
Network ID — Matches the expected network
Phase-2 (Plutus Script Execution)
For transactions containing Plutus scripts (V1/V2/V3):
- Script data hash — Matches the hash of redeemers + datums + cost models
- Collateral — Sufficient collateral provided (150% of estimated fees in Conway)
- Execution units — Each redeemer's CPU and memory within budget
- Script evaluation — Each script is executed via the dugite-uplc CEK machine with the appropriate cost model
- Block budget — Total execution units across all transactions do not exceed block limits
Scripts are evaluated in parallel using rayon when the parallel-verification feature is enabled (default).
Validation Error Types
The ValidationError enum covers 50+ error variants across all categories: structural, UTxO, fees, witnesses, time, scripts, collateral, Plutus, era-gating, certificates, governance, datums, withdrawals, network, and auxiliary data.
Certificate Processing
Dugite processes all Shelley through Conway certificate types:
| Certificate | Description |
|---|---|
| StakeRegistration | Register a stake credential (deposit required) |
| StakeDeregistration | Deregister a stake credential (deposit refunded) |
| StakeDelegation | Delegate stake to a pool |
| PoolRegistration | Register a new stake pool |
| PoolRetirement | Schedule pool retirement at a future epoch |
| RegDRep | Register a delegated representative (Conway) |
| UnregDRep | Deregister a DRep (Conway) |
| UpdateDRep | Update DRep metadata anchor (Conway) |
| VoteDelegation | Delegate voting power to a DRep (Conway) |
| StakeVoteDelegation | Combined stake + vote delegation (Conway) |
| RegStakeDeleg | Combined registration + stake delegation (Conway) |
| RegStakeVoteDeleg | Combined registration + stake + vote delegation (Conway) |
| CommitteeHotAuth | Authorize a hot key for a constitutional committee member (Conway) |
| CommitteeColdResign | Resign a constitutional committee cold key (Conway) |
| MoveInstantaneousRewards | Transfer between treasury and reserves (pre-Conway) |
Governance (CIP-1694)
The GovernanceState tracks all Conway-era governance:
DRep Lifecycle
- Registration — DReps register with a deposit, becoming eligible to vote
- Activity tracking — DReps must vote within
drepActivityepochs or become inactive - Expiration — Inactive DReps' delegated stake counts as abstaining
- Delegation — Stake credentials delegate voting power to DReps, AlwaysAbstain, or AlwaysNoConfidence
Constitutional Committee
- Hot key authorization — Cold keys authorize hot keys for voting
- Member expiration — Each member has an epoch-based term limit
- Quorum — Threshold fraction of non-expired, non-resigned members must approve
Governance Actions
Seven action types with per-type ratification thresholds:
| Action | DRep Threshold | SPO Threshold | CC Required |
|---|---|---|---|
| ParameterChange | Varies by param group (4 groups) | Varies by param group (5 groups) | Yes |
| HardForkInitiation | DRep threshold | SPO threshold | Yes |
| TreasuryWithdrawals | DRep threshold | No | Yes |
| NoConfidence | DRep threshold | SPO threshold | No |
| UpdateCommittee | DRep threshold | SPO threshold | No (if NoConfidence) |
| NewConstitution | DRep threshold | No | Yes |
| InfoAction | No threshold | No threshold | No |
Ratification
Ratification uses a two-epoch delay: proposals and votes from epoch E are considered at the E+1 → E+2 boundary using a frozen RatificationSnapshot. This prevents mid-epoch voting from affecting the current epoch's ratification. Thresholds use exact rational arithmetic via u128 cross-multiplication.
Epoch Transitions
At each epoch boundary, process_epoch_transition() follows the Haskell NEWEPOCH STS rule:
flowchart TD
NE[NEWEPOCH] --> RUPD[Apply pending RUPD<br/>treasury += deltaT<br/>reserves -= deltaR<br/>credit rewards]
RUPD --> SNAP[SNAP<br/>Rotate mark → set → go<br/>Capture current fees]
SNAP --> POOLREAP[POOLREAP<br/>Process pool retirements<br/>Refund deposits]
POOLREAP --> RAT[RATIFY<br/>Governance ratification<br/>Enact approved actions]
RAT --> RESET[Reset block counters<br/>Clear RUPD state]
Reward Distribution (RUPD)
Rewards follow a deferred schedule matching Haskell's pulsing reward computation:
- Epoch E → E+1: Compute RUPD (monetary expansion + fees - treasury cut)
- Epoch E+1 → E+2: Apply RUPD (credit rewards to accounts, update treasury/reserves)
The reward calculation uses the "go" snapshot (two epochs old) for stake distribution, ensuring a stable base for computation.
Stake Snapshots
The mark/set/go model ensures different subsystems use consistent, non-overlapping snapshots:
| Snapshot | Age | Used For |
|---|---|---|
| Mark | Current epoch boundary | Future leader election (2 epochs later) |
| Set | Previous epoch boundary | Current epoch leader election |
| Go | Two epochs ago | Current epoch reward distribution |
UTxO Storage
UtxoStore
The persistent UTxO set wraps a dugite-lsm LSM tree:
- 36-byte keys — 32-byte transaction hash + 4-byte output index (big-endian)
- Bincode values —
TransactionOutputserialized via bincode - Address index — In-memory
HashMap<Address, HashSet<TransactionInput>>for N2C LocalStateQueryGetUTxOByAddressefficiency - Bloom filters — 10 bits per key (~1% false positive rate) for fast negative lookups during validation
DiffSeq (Rollback Support)
Each block produces a UtxoDiff recording inserted and deleted UTxOs. The DiffSeq holds the last k=2160 diffs, enabling O(1) rollback by applying diffs in reverse without reloading snapshots.
LedgerSeq (Anchored State Sequence)
LedgerSeq implements Haskell's V2 LedgerDB architecture:
- Anchor — One full
LedgerStateat the immutable tip (persisted to disk) - Volatile deltas — Per-block
LedgerDeltafor the last k blocks - Checkpoints — Full state snapshots every ~100 blocks for fast reconstruction
- Rollback — Drop trailing deltas and reconstruct from the nearest checkpoint
This avoids the 17-34 GB memory overhead of storing k full state copies.
CompositeUtxoView (Mempool Support)
All validate_transaction_* functions accept any UtxoLookup implementation. The CompositeUtxoView layers a mempool overlay on top of the on-chain UTxO set, enabling validation of chained mempool transactions (where one tx spends outputs of another unconfirmed tx) without mutating the live ledger state.
Consensus
Dugite implements the Ouroboros Praos consensus protocol, the proof-of-stake protocol used by Cardano since the Shelley era.
Ouroboros Praos Overview
Ouroboros Praos divides time into fixed-length slots. Each slot, a slot leader is selected based on their stake proportion. The leader is entitled to produce a block for that slot. Key properties:
- Slot-based — Time is divided into slots (1 second each on mainnet)
- Epoch-based — Slots are grouped into epochs (432000 slots / 5 days on mainnet)
- Stake-proportional — The probability of being elected is proportional to the pool's active stake
- Private leader selection — Only the pool operator knows if they are elected (until they publish the block)
Slot Leader Election
VRF-Based Selection
Each slot, the pool operator evaluates a VRF (Verifiable Random Function) using:
- Their VRF signing key
- The slot number
- The epoch nonce
The VRF produces:
- A VRF output — A deterministic pseudo-random value
- A VRF proof — A proof that the output was correctly computed
Leader Threshold
The VRF output is compared against a threshold derived from:
- The pool's relative stake (sigma)
- The active slot coefficient (f = 0.05 on mainnet)
The threshold is computed using the phi function:
phi(sigma) = 1 - (1 - f)^sigma
A slot leader is elected if VRF_output < phi(sigma).
VRF Exact Rational Arithmetic
The leader check is a critical consensus operation — any deviation from the Haskell reference implementation would cause a node to disagree on which blocks are valid. Dugite uses exact 34-digit fixed-point arithmetic via dashu-int IBig, matching Haskell's FixedPoint E34 type exactly. No floating-point operations are used anywhere in the VRF computation path.
Era-dependent VRF modes:
| Era | Protocol Version | VRF Output Derivation | certNatMax |
|---|---|---|---|
| Shelley — Alonzo (TPraos) | proto < 7 | Raw 64-byte VRF output | 2^512 |
| Babbage — Conway (Praos) | proto >= 7 | Blake2b-256("L" || output) | 2^256 |
In TPraos mode (Shelley through Alonzo), the raw 64-byte VRF output is used directly for the leader check, with a certNatMax of 2^512 defining the output space. In Praos mode (Babbage onward), the VRF output is hashed with Blake2b-256("L" || output) to produce a 32-byte value, reducing certNatMax to 2^256. The "L" prefix distinguishes the leader VRF output from the nonce VRF output (which uses "N").
Mathematical primitives:
ln(1 + x)— Uses the Euler continued fraction expansion, matching Haskell'slncffunction. This converges for allx >= 0, unlike Taylor series which has a limited radius of convergence.taylorExpCmp— Computesexp()via Taylor series with rigorous error bounds, enabling early termination when the comparison result can be determined without computing the full expansion. This avoids unnecessary precision in the common case where the VRF output is far from the threshold.
Epoch Nonce
The epoch nonce is computed at each epoch boundary:
epoch_nonce = hash(candidate_nonce || lab_nonce)
Where:
candidate_nonceis the evolving nonce frozen at the stability window boundary of the previous epochlab_nonceis a hash derived from the previous epoch's first block (the "laboratory" nonce)
The initial nonce is derived from the Shelley genesis hash.
Nonce Establishment
The nonce lifecycle follows a precise sequence across epoch boundaries:
- Evolving nonce — Accumulates VRF nonce contributions from every block:
evolving_nonce = hash(prev_evolving_nonce || hash(vrf_nonce_output)) - Candidate nonce — The evolving nonce is frozen (snapshotted) at the stability window boundary within each epoch. After this point, new VRF contributions only affect the evolving nonce, not the candidate.
- Epoch nonce — At the epoch boundary, the new epoch nonce is computed as
hash(candidate_nonce_from_prev_epoch || lab_nonce).
flowchart LR
A["Block VRF<br/>contributions"] -->|"accumulated<br/>every block"| B["Evolving<br/>Nonce"]
B -->|"frozen at<br/>stability window"| C["Candidate<br/>Nonce"]
C -->|"hash(candidate ∥ lab)"| D["Epoch Nonce<br/>(next epoch)"]
Nonce availability after startup:
The epoch nonce and its accumulators (evolving_nonce, candidate_nonce, last_epoch_block_nonce) are serialized in the ledger snapshot and considered authoritative immediately after a snapshot load or Mithril import. This matches Haskell cardano-node's treatment of praosStateEpochNonce, which is read directly from the deserialized PraosState with no warm-up or "established" gate.
Era-Dependent Nonce Stabilisation Window
The stability window determines how early in an epoch the candidate nonce is frozen. This varies by era:
| Era | Protocol Version | Stability Window |
|---|---|---|
| Shelley — Babbage | proto < 10 | 3k/f slots |
| Conway | proto >= 10 | 4k/f slots |
Where k is the security parameter (2160 on mainnet) and f is the active slot coefficient (0.05 on mainnet). The longer Conway window provides additional time for nonce contributions to accumulate, improving randomness quality.
Chain Selection
When multiple valid chains exist, Ouroboros Praos selects the chain with the most blocks (longest chain rule). Dugite implements:
- Chain comparison — Compare the block height of competing chains
- Rollback support — Roll back up to k=2160 blocks to switch to a longer chain
- Immutability — Blocks deeper than k are considered final
Epoch Transitions
At each epoch boundary, Dugite performs:
Stake Snapshot Rotation
Dugite uses the mark/set/go snapshot model:
- Mark — The current epoch boundary snapshot (will be used for leader election two epochs from now)
- Set — The previous epoch's mark (used for leader election in the current epoch)
- Go — Two epochs ago (used for reward distribution in the current epoch)
At each epoch boundary:
- Go becomes the active snapshot for reward distribution
- Set moves to go
- Mark moves to set
- A new mark is taken from the current ledger state
flowchart LR
subgraph "Epoch N Boundary"
direction TB
L["Current Ledger<br/>State"] -->|"snapshot"| M["Mark"]
M -->|"rotate"| S["Set"]
S -->|"rotate"| G["Go"]
end
S -.- LE["Leader Election<br/>(epoch N)"]
G -.- RD["Reward Distribution<br/>(epoch N)"]
Snapshot Establishment
After a node starts, the snapshots are not immediately trustworthy for block production:
snapshots_establishedrequires at least 3 live (post-replay) epoch transitions before returning true. This ensures that all three snapshot positions (mark, set, go) have been populated by the running node with precise stake calculations.- Replay-built snapshots may contain approximate stake values due to differences in reward calculation during fast replay versus live operation. These are sufficient for validation but not authoritative for forging.
- VRF leader eligibility failures are non-fatal until snapshots are fully established. During the establishment period, a pool may fail leader checks because the stake distribution in the snapshot does not yet reflect the true on-chain state. The node logs these failures but continues normal operation.
Reward Calculation and Distribution
At each epoch boundary, rewards are calculated and distributed:
- Monetary expansion — New ADA is created from the reserves based on the monetary expansion rate
- Fee collection — Transaction fees from the epoch are collected
- Treasury cut — A fraction (tau) of rewards goes to the treasury
- Pool rewards — Remaining rewards are distributed to pools based on their performance
- Member distribution — Pool rewards are split between the operator and delegators based on pool parameters (cost, margin, pledge)
Validation Checks
Dugite validates the following consensus-level properties:
KES Period Validation
The KES (Key Evolving Signature) period in the block header must be within the valid range for the operational certificate:
opcert_start_kes_period <= current_kes_period < opcert_start_kes_period + max_kes_evolutions
VRF Verification
Full VRF verification includes:
- VRF key binding —
blake2b_256(header.vrf_vkey)must match the pool's registeredvrf_keyhash - VRF proof verification — The VRF proof is cryptographically verified against the VRF public key
- Leader eligibility — The VRF leader value is checked against the Praos threshold for the pool's relative stake using the phi function
Operational Certificate Verification
The operational certificate's Ed25519 signature is verified against the raw bytes signable format (matching Haskell's OCertSignable):
signable = hot_vkey(32 bytes) || counter(8 bytes BE) || kes_period(8 bytes BE)
signature = sign(cold_skey, signable)
The counter must be monotonically increasing per pool to prevent certificate replay.
KES Signature Verification
Block headers are signed using the Sum6Kes scheme (depth-6 binary sum composition over Ed25519), via the kes-summed-ed25519 crate. The signing key occupies a 612-byte buffer (608 bytes of key material + a 4-byte period counter); the upstream Sum6Kes::drop implementation zeroizes this buffer on drop, so any code that needs to retain key bytes past a Sum6Kes value's lifetime must copy them out first. The KES key is evolved to the correct period offset from the operational certificate's start period. Verification checks:
- The KES signature over the header body bytes is valid
- The KES period matches the expected value for the block's slot
Slot Leader Eligibility
The VRF proof is checked to confirm the block producer was indeed elected for the slot, given the epoch nonce and their pool's stake.
Networking
Dugite implements the full Ouroboros network protocol stack, supporting both Node-to-Node (N2N) and Node-to-Client (N2C) communication.
Protocol Stack
flowchart TB
subgraph N2N ["Node-to-Node (TCP)"]
HS[Handshake V14/V15]
CSP[ChainSync<br/>Headers]
BFP[BlockFetch<br/>Block Bodies]
TX[TxSubmission2<br/>Transactions]
KA[KeepAlive<br/>Liveness]
end
subgraph N2C ["Node-to-Client (Unix Socket)"]
HSC[Handshake]
LCS[LocalChainSync<br/>Block Delivery]
LSQ[LocalStateQuery<br/>Ledger Queries]
LTS[LocalTxSubmission<br/>Submit Transactions]
LTM[LocalTxMonitor<br/>Mempool Queries]
end
MUX[Multiplexer] --> N2N
MUX --> N2C
Relay Node Architecture
flowchart TB
subgraph Inbound ["Inbound Connections"]
IN1[Peer A] -->|N2N| MUX_IN[Multiplexer]
IN2[Peer B] -->|N2N| MUX_IN
IN3[Wallet] -->|N2C| MUX_N2C[N2C Server]
end
subgraph Outbound ["Outbound Connections"]
MUX_OUT[Multiplexer] -->|ChainSync| PEER1[Bootstrap Peer]
MUX_OUT -->|BlockFetch| PEER1
MUX_OUT -->|TxSubmission| PEER1
end
subgraph Core ["Node Core"]
PM[Peer Manager<br/>Cold→Warm→Hot]
MP[Mempool<br/>Tx Validation]
CDB[(ChainDB)]
LS[Ledger State]
CONS[Consensus<br/>Ouroboros Praos]
end
MUX_IN -->|ChainSync| CDB
MUX_IN -->|BlockFetch| CDB
MUX_IN -->|TxSubmission| MP
MUX_N2C -->|LocalStateQuery| LS
MUX_N2C -->|LocalTxSubmission| MP
MUX_N2C -->|LocalTxMonitor| MP
PEER1 -->|blocks| CDB
CDB --> LS
LS --> CONS
PM -->|manage| MUX_OUT
PM -->|manage| MUX_IN
Node-to-Node (N2N) Protocol
N2N connections use TCP and carry multiple mini-protocols over a multiplexed connection.
Handshake (V14/V15)
The N2N handshake negotiates the protocol version and network parameters:
- Protocol version V14 (Plomin HF) and V15 (SRV DNS support)
- Network magic number
- Diffusion mode:
InitiatorOnlyorInitiatorAndResponder - Peer sharing flags
ChainSync
The ChainSync mini-protocol synchronizes block headers between peers:
- Client mode: Requests headers sequentially from a peer to track the chain
- Server mode: Serves headers to connected peers, with per-peer cursor tracking
Key messages:
MsgFindIntersect— Find a common chain pointMsgRequestNext— Request the next headerMsgRollForward— Header deliveredMsgRollBackward— Chain reorganizationMsgAwaitReply— Peer has no new headers (at tip)
BlockFetch
The BlockFetch mini-protocol retrieves block bodies by hash:
- Client mode: Requests ranges of blocks from peers
- Server mode: Serves blocks to peers, validates block existence before serving
Key messages:
MsgRequestRange— Request blocks in a slot rangeMsgBlock— Block deliveredMsgNoBlocks— Requested blocks not availableMsgBatchDone— End of batch
TxSubmission2
The TxSubmission2 mini-protocol propagates transactions between peers:
- Bidirectional handshake (
MsgInit) - Flow-controlled transaction exchange with ack/req counts
- Inflight tracking per peer
- Mempool integration for serving transaction IDs and bodies
KeepAlive
The KeepAlive mini-protocol maintains connection liveness with periodic heartbeat messages.
PeerSharing
The PeerSharing mini-protocol enables gossip-based peer discovery. Peers exchange addresses of other known peers to help the network self-organize.
Node-to-Client (N2C) Protocol
N2C connections use Unix domain sockets and serve local clients (wallets, CLI tools). The N2C handshake supports versions V16-V22 (Conway era) with automatic detection of the Haskell bit-15 version encoding used by cardano-cli 10.x.
LocalStateQuery
Supports all 39 Shelley BlockQuery tags (0-38) plus cross-era queries, providing full compatibility with cardano-node. The query protocol uses an acquire/query/release pattern:
MsgAcquire— Lock the ledger state at the current tipMsgQuery— Execute queries against the locked stateMsgRelease— Release the lock
All BlockQuery messages are wrapped in the Hard Fork Combinator (HFC) envelope. Results from era-specific BlockQuery tags are returned inside an array(1) success wrapper, while QueryAnytime and QueryHardFork results are returned unwrapped.
Shelley BlockQuery Tags 0-38
| Tag | Query | Description |
|---|---|---|
| 0 | GetLedgerTip | Current slot, hash, and block number |
| 1 | GetEpochNo | Active epoch number |
| 2 | GetCurrentPParams | Live protocol parameters (positional array(31) CBOR encoding matching Haskell ConwayPParams EncCBOR) |
| 3 | GetProposedPParamsUpdates | Proposed parameter updates (empty map in Conway) |
| 4 | GetStakeDistribution | Pool stake distribution with pledge |
| 5 | GetNonMyopicMemberRewards | Estimated rewards per pool for given stake amounts |
| 6 | GetUTxOByAddress | UTxO set filtered by address (Cardano wire format Map<[tx_hash, index], {0: addr, 1: value, 2: datum}>) |
| 7 | GetUTxOWhole | Entire UTxO set (expensive; used by testing tools) |
| 8 | DebugEpochState | Simplified epoch state summary (treasury, reserves, active stake totals) |
| 9 | GetCBOR | Meta-query that wraps the result of an inner query in CBOR tag(24), returning raw bytes |
| 10 | GetFilteredDelegationsAndRewardAccounts | Delegation targets and reward balances for a set of stake credentials |
| 11 | GetGenesisConfig | System start, epoch length, slot length, and security parameter |
| 12 | DebugNewEpochState | Simplified new epoch state summary (epoch number, block count, snapshot state) |
| 13 | DebugChainDepState | Chain-dependent state summary (last applied block, operational certificate counters) |
| 14 | GetRewardProvenance | Reward calculation provenance: reward pot, treasury tax rate, total active stake, per-pool reward breakdown |
| 15 | GetUTxOByTxIn | UTxO set filtered by transaction inputs |
| 16 | GetStakePools | Set of all registered pool key hashes |
| 17 | GetStakePoolParams | Registered pool parameters (owner, cost, margin, pledge, relays, metadata) |
| 18 | GetRewardInfoPools | Per-pool reward breakdown: relative stake, leader and member reward splits, pool margin, fixed cost, and performance metrics |
| 19 | GetPoolState | QueryPoolStateResult encoded as array(4): [poolParams, futurePoolParams, retiring, deposits] |
| 20 | GetStakeSnapshots | Mark/set/go stake snapshots used for leader schedule calculation |
| 21 | GetPoolDistr | Pool stake distribution with VRF verification key hashes |
| 22 | GetStakeDelegDeposits | Deposit amounts per registered stake credential |
| 23 | GetConstitution | Constitution anchor (URL + hash) and optional guardrail script hash |
| 24 | GetGovState | ConwayGovState encoded as array(7) CBOR: active proposals, committee state, constitution, current/previous protocol parameters, future parameters, and DRep pulse state |
| 25 | GetDRepState | Registered DReps with their delegation counts and deposit balances (supports credential filter) |
| 26 | GetDRepStakeDistr | Total delegated stake per DRep (lovelace) |
| 27 | GetCommitteeMembersState | Constitutional committee members, iterating committee_expiration entries with hot_credential_type for each member |
| 28 | GetFilteredVoteDelegatees | Vote delegation map per stake credential |
| 29 | GetAccountState | Treasury and reserves balances |
| 30 | GetSPOStakeDistr | Per-pool stake distribution filtered by a set of pool IDs |
| 31 | GetProposals | Active governance proposals with optional governance action ID filter |
| 32 | GetRatifyState | Enacted and expired proposals along with the ratify_delayed flag |
| 33 | GetFuturePParams | Pending protocol parameter changes scheduled for the next epoch (if any) |
| 34 | GetLedgerPeerSnapshot | SPO relay addresses weighted by relative stake, used for P2P ledger-based peer discovery |
| 35 | QueryStakePoolDefaultVote | Default vote per pool derived from its DRep delegation (AlwaysAbstain, AlwaysNoConfidence, or specific DRep vote) |
| 36 | GetPoolDistr2 | Extended pool distribution including total_active_stake alongside per-pool entries |
| 37 | GetStakeDistribution2 | Extended stake distribution including total_active_stake |
| 38 | GetMaxMajorProtocolVersion | Maximum supported major protocol version — sourced from the loaded network's protocol-version config, not hardcoded (currently 10 on mainnet, 11 on preview) |
Cross-Era Queries
In addition to the Shelley BlockQuery tags, the following queries operate outside the HFC era-specific envelope:
| Query | Description |
|---|---|
| GetCurrentEra | Active era (Byron through Conway) |
| GetChainBlockNo | Current chain height, WithOrigin encoded as [1, blockNo] for At or [0] for Origin |
| GetChainPoint | Current tip point, encoded as [] for Origin or [slot, hash] for a specific point |
| GetSystemStart | Network genesis time as UTCTime encoded [year, dayOfYear, picosOfDay] |
| GetEraHistory | Indefinite array of EraSummary entries (Byron safe_zone = k*2, Shelley+ safe_zone = 3k/f) |
CBOR Encoding Notes
- PParams are encoded as a positional
array(31)with integer keys 0-33, matching Haskell'sEncCBORinstance (not JSON string keys). - CBOR Sets (e.g., pool IDs, stake key owners) use
tag(258)and elements must be sorted for canonical encoding. - Value encoding: plain integer for ADA-only UTxOs,
[coin, multiasset_map]for multi-asset UTxOs.
LocalTxSubmission
Submits transactions from local clients to the node's mempool:
| Message | Description |
|---|---|
MsgSubmitTx | Submit a transaction (era ID + CBOR bytes) |
MsgAcceptTx | Transaction accepted into mempool |
MsgRejectTx | Transaction rejected with reason |
Submitted transactions undergo both Phase-1 (structural) and Phase-2 (Plutus script) validation before mempool admission.
LocalTxMonitor
Monitors the transaction mempool:
| Message | Description |
|---|---|
MsgAcquire | Acquire a mempool snapshot |
MsgHasTx | Check if a transaction is in the mempool |
MsgNextTx | Get the next transaction from the mempool |
MsgGetSizes | Get mempool capacity, size, and transaction count |
P2P Networking
Dugite implements the full Ouroboros P2P peer selection governor, enabled by default (EnableP2P: true). The governor manages peer connections through a target-driven state machine that continuously maintains optimal connectivity.
Diffusion Mode
The DiffusionMode config field controls how the node participates in the network:
InitiatorAndResponder(default) — Full relay mode. The node opens a listening port and accepts inbound N2N connections from other peers, in addition to making outbound connections. This is the correct mode for relay nodes.InitiatorOnly— Block producer mode. The node only makes outbound connections to its configured relays and never opens a listening port. This prevents direct internet exposure of block producers.
Peer Sharing
The PeerSharing mini-protocol enables gossip-based peer discovery. When enabled, the node exchanges addresses of known routable peers with connected peers.
Peer sharing behaviour is auto-configured by default:
- Relays — Peer sharing is enabled, allowing the node to both request and serve peer addresses.
- Block producers — Peer sharing is disabled (when
--shelley-kes-keyis provided) to avoid leaking the BP's network position.
Override with the PeerSharing config field (true/false) if needed.
The PeerSharing protocol filters out non-routable addresses (RFC1918, CGNAT, loopback, link-local, IPv6 ULA) before sharing.
Peer Manager
The peer manager (crates/dugite-network/src/peer/manager.rs) classifies peers into four
temperature states, mirroring Haskell's PeerStatus (Cold < Cooling < Warm < Hot):
- Cold — Known but not connected; candidate for promotion
- Cooling — Cold in effect but the connection still lingers (the governor's analogue of the
connection manager's
TerminatingState— dugite's version of TCPTIME_WAIT). Not eligible for re-promotion until it transitions toCold. - Warm — TCP connected, keepalive running, but not actively syncing
- Hot — Fully active with ChainSync, BlockFetch, and TxSubmission2
Peer Lifecycle
stateDiagram-v2
[*] --> Cold: Discovered
Cold --> Warm: TCP connect + handshake
Warm --> Hot: Mini-protocols activated
Hot --> Warm: Demotion (poor performance / churn)
Warm --> Cold: Disconnection / backoff
Hot --> Cooling: Forceful disconnect
Cooling --> Cold: Cooldown elapsed
Peer Sources
Peers enter the Cold pool from four sources:
| Source | Description |
|---|---|
| Topology | Bootstrap peers, local roots, and public roots from the topology file |
| DNS | A/AAAA resolution of hostname-based topology entries |
| Ledger | SPO relay addresses from pool registration certificates (after useLedgerAfterSlot) |
| PeerSharing | Addresses received via the gossip protocol from connected peers |
Peer Selection & Scoring
Peers are ranked using a composite score:
score = 0.4 × reputation + 0.4 × latency_score + 0.2 × failure_score
Where:
- Reputation — 0.0 (worst) to 1.0 (best), adjusted +0.01 per success, -0.1 per failure
- Latency score —
1 / (1 + ms/200), based on EWMA latency (smoothing α=0.3) - Failure score —
max(1.0 - failures×0.1, 0.0), failure counts decay (halve every 5 minutes)
Separately from this connection-quality score, PeerManager also tracks a per-peer EWMA
fetch bandwidth ("GSV"/"fetchyness", bytes/sec from completed BlockFetch ranges). This is
what the single-fetcher BlockFetch worker uses to pick the fastest peer during bulk sync — see
Sync Pipeline.
Failure Handling
- Exponential backoff on connection failures: 5s → 10s → 20s → 40s → 80s → 160s (capped), with ±2s random fuzz
- Max cold failures: 5 consecutive failures before a peer is evicted from the peer table
- Failure decay: Failure counts halve every 5 minutes, allowing peers to recover reputation over time
Inbound Connections
- Per-IP token bucket rate limiting for DoS protection
- N2N server handles handshake, ChainSync, BlockFetch, KeepAlive, TxSubmission2, and PeerSharing
DiffusionModecontrols whether inbound connections are accepted
P2P Governor
The governor runs as a tokio task on a 2-second interval, continuously evaluating peer counts against configured targets and emitting promotion/demotion/connect/disconnect actions. (Churn — periodic rotation of otherwise-healthy peers — runs on its own, much longer cadence; see below.)
Target Counts
The governor maintains six independent target counts (matching cardano-node defaults):
| Target | Default | Description |
|---|---|---|
TargetNumberOfKnownPeers | 150 | Total peers in the peer table (cold + warm + hot) |
TargetNumberOfEstablishedPeers | 30 | Warm + hot peers (TCP connected) |
TargetNumberOfActivePeers | 20 | Hot peers (fully syncing) |
TargetNumberOfKnownBigLedgerPeers | 15 | Known big ledger peers |
TargetNumberOfEstablishedBigLedgerPeers | 10 | Established big ledger peers |
TargetNumberOfActiveBigLedgerPeers | 5 | Active big ledger peers |
During Genesis-mode sync (PreSyncing/Syncing), a separate, smaller set of sync_target_*
values applies instead (active=5, established=10, known=150), concentrating the peer set on
big-ledger peers until the node catches up.
When any target is not met, the governor promotes peers to fill the deficit. When any target is exceeded, the governor demotes the lowest-scoring surplus peers. Local root peers are never demoted.
Sync-State-Aware Targeting
The governor adjusts behaviour based on sync state:
- PreSyncing / Syncing — Big ledger peers are prioritised for fast block download
- CaughtUp — Normal target enforcement with balanced peer selection
Churn
The governor periodically rotates a subset of peers to discover better alternatives:
- Normal (caught-up) cadence: every 3300s (55 minutes), matching cardano-node's
ChurnIntervalNormalSecs - Bulk-sync cadence: every 900s (15 minutes) while behind tip, matching
ChurnIntervalSyncSecs— more aggressive so peers with poor block-fetch performance are shed faster - Both cadences are reloadable at runtime via
SIGHUP - Local root peers are exempt from churn
- Churn ensures the node explores the peer landscape rather than settling on suboptimal connections
See P2P Governor for the full peer-manager and governor implementation details.
Prometheus Metrics
The P2P subsystem exports the following metrics:
| Metric | Description |
|---|---|
dugite_diffusion_mode | Current diffusion mode (0=InitiatorOnly, 1=InitiatorAndResponder) |
dugite_peer_sharing_enabled | Whether peer sharing is active (gauge: 0 or 1) |
dugite_peers_cold | Number of cold (known, unconnected) peers |
dugite_peers_warm | Number of warm (established) peers |
dugite_peers_hot | Number of hot (active) peers |
Peer Discovery
Peers are discovered through multiple channels:
- Topology file — Bootstrap peers, local roots, and public roots
- PeerSharing protocol — Gossip-based discovery from connected peers
- Ledger-based discovery — SPO relay addresses extracted from pool registration certificates
Ledger-Based Peer Discovery
Once the node has synced past the slot threshold configured by useLedgerAfterSlot in the topology file, it activates ledger-based peer discovery. This mechanism extracts SPO relay addresses directly from pool registration parameters (pool_params) stored in the ledger state.
The discovery process runs on a periodic 5-minute interval and works as follows:
- Slot check — The current ledger tip slot is compared against
useLedgerAfterSlot. If the topology sets this value to a negative number or omits it entirely, ledger peer discovery remains disabled. - Relay extraction — All registered pool parameters are iterated, extracting relay entries of three types:
SingleHostAddr— IPv4 address and portSingleHostName— DNS hostname and portMultiHostName— DNS hostname with default port 3001
- Sampling — A deterministic subset (up to 20 relays) is sampled from the full relay set to avoid resolving thousands of addresses at once. The sample offset rotates based on the current slot for coverage diversity.
- DNS resolution — Hostnames are resolved to socket addresses via async DNS lookup.
- Peer manager integration — Resolved addresses are added as cold peers with
PeerSource::Ledgerclassification, alongside existing bootstrap and public root peers.
As pool registrations change over time (new pools register, existing pools update relay addresses, pools retire), the ledger peer set evolves dynamically. This provides a protocol-native discovery mechanism that does not depend on any centralized directory.
Block Relay
Dugite implements full relay node behavior, propagating blocks received from upstream peers to all downstream N2N connections. This ensures that blocks flow through the network without requiring every node to sync directly from the block producer.
Broadcast Architecture
Block propagation uses a tokio::sync::broadcast channel with a capacity of 512 announcements
(raised from an original 64 to absorb burst fork events without triggering the receiver's lagged
path, which would otherwise hand a downstream peer an incorrect rollback point). A parallel
rollback-announcement channel has capacity 256 (raised from 16). The architecture has three
components:
- Sender — The node core holds a
broadcast::Sender<BlockAnnouncement>obtained from the N2N server at startup. When the sync pipeline processes new blocks or the forge module produces a new block, it sends an announcement containing the slot, block hash, and block number. - Receivers — Each N2N server connection spawns with its own
broadcast::Receiversubscription. The connection handler usestokio::select!to concurrently service mini-protocol messages and listen for block announcements. - Delivery — When a downstream peer is waiting at the tip (having received
MsgAwaitReplyfrom ChainSync), an incoming block announcement triggers aMsgRollForwardmessage to that peer, along with the block header. The peer can then fetch the full block body via BlockFetch.
Relay vs. Forger Announcements
Both synced and forged blocks flow through the same broadcast channel:
- Synced blocks — When the pipelined ChainSync client receives blocks from an upstream peer and the node is following the tip (strict mode), each batch's final block is announced to all downstream connections. This enables relay behavior where blocks received from one upstream peer propagate to all other connected peers.
- Forged blocks — When the block producer creates a new block, it is announced through the same channel after being written to ChainDB and applied to the ledger.
A parallel broadcast::Sender<RollbackAnnouncement> handles chain rollbacks, sending MsgRollBackward to downstream peers when the node's chain selection switches to a different fork.
Lagged Receivers
If a downstream peer falls behind (e.g., slow network or processing), the broadcast channel's bounded capacity means the receiver may lag. Lagged receivers skip missed announcements and log the gap, ensuring a slow peer does not block propagation to others.
Multiplexer
All mini-protocols run over a single TCP connection (N2N) or Unix socket (N2C), multiplexed by protocol ID:
| Protocol ID | Mini-Protocol |
|---|---|
| 0 | Handshake |
| 2 | ChainSync (N2N) |
| 3 | BlockFetch (N2N) |
| 4 | TxSubmission2 (N2N) |
| 8 | KeepAlive (N2N) |
| 10 | PeerSharing (N2N) |
| 5 | LocalChainSync (N2C) |
| 6 | LocalTxSubmission (N2C) |
| 7 | LocalStateQuery (N2C) |
| 9 | LocalTxMonitor (N2C) |
The multiplexer uses length-prefixed frames with protocol ID headers, matching the Ouroboros specification.
P2P Governor
This document describes Dugite's peer management architecture, implementing the Ouroboros P2P peer selection governor.
Architecture
Two modules implement peer management in dugite-network:
PeerManager (manager.rs)
The data layer. Tracks every known peer in a HashMap<SocketAddr, PeerInfo> keyed by socket
address, with PeerInfo.state holding each peer's temperature.
| Feature | Description |
|---|---|
PeerState | Cold < Cooling < Warm < Hot, mirroring Haskell's PeerStatus — Cooling is a lingering-connection state between Hot and Cold (dugite's TCP TIME_WAIT analogue), not a fourth independent bucket |
PeerSource | Topology (topology-file config), Dns (SRV/A/AAAA resolution), Ledger (SPO relays from pool_params), PeerSharing (gossip) |
| Big-ledger-peer / local-root tracking | Tracked as separate HashSet<SocketAddr> / group lists passed into the governor, not as a per-peer category enum |
| Fetch bandwidth ("GSV"/"fetchyness") | EWMA bytes/sec per peer from completed BlockFetch ranges, used to rank peers for the single bulk-sync fetch slot (see Sync Pipeline) |
| Reputation scoring | 0.4×reputation + 0.4×latency_score + 0.2×failure_score; +0.01 per success, -0.1 per failure |
| Exponential backoff | 5s → 10s → 20s → 40s → 80s → 160s (capped) ± 2s fuzz on connection failure |
| Inbound connection limit | Configurable max inbound connections |
DiffusionMode | InitiatorOnly / InitiatorAndResponder |
| Failure-count time decay | Halves every 5 minutes |
Governor (governor.rs)
The policy layer. Its decision function is called on a 2-second tokio::interval in
dugite-node; churn timers (below) run on their own, much longer independent cadences checked on
every tick.
| Feature | Description |
|---|---|
PeerTargets | root/known/established/active + BLP variants |
| Sync-state-aware target switching | A separate, smaller sync_target_* set applies during Genesis-mode PreSyncing/Syncing |
| Big-ledger-peer promotion priority | BLPs promoted first during sync |
| Active (hot) peer target enforcement | Promotes/demotes to meet active target |
| Established (warm+hot) target enforcement | Maintains established peer count |
| Surplus reduction | Demote/disconnect lowest reputation, local-root protected |
| Three independent churn timers | Hot churn (rotate one hot peer), cold churn (forget lowest-reputation cold peers once the pool exceeds 150% of max_cold), warm churn (quality-based rotation) |
| Default targets | active=20, established=30, known=150 (matching cardano-node) |
Wiring
The governor runs inline in the main select! loop in node/mod.rs — not as a separate spawned
task. Every 2 seconds (governor_ticker) it:
- Acquires a read lock on
Arc<RwLock<PeerManager>>, snapshots local-root groups, the big-ledger-peer set, and the peer currently holding the BlockFetch fetch slot (so the governor never demotes the peer actively downloading blocks), then callsgovernor.compute_actions_with_blp(...), which returns aVec<GovernorAction>. Churn decisions are folded into this same call — there is no separate churn step. GovernorAction::PromoteToWarm(addr)is dispatched via a backgroundtokio::spawn(throughlifecycle.spawn_connect(...)) so a slow TCP connect never blocks the block-processing side of the sameselect!loop.- All other actions (
PromoteToHot,DemoteToWarm,DemoteToCold,ForgetPeer,PeerShareRequest,DiscoverMore) are fast, O(1) operations applied inline under a write lock on the same tick.
Peer Selection State Machine
Peers progress through a formal state machine (PeerState, mirroring Haskell's PeerStatus):
stateDiagram-v2
[*] --> Cold
Cold --> Warm: TCP connect + handshake
Warm --> Hot: Activate mini-protocols
Hot --> Warm: Deactivate mini-protocols
Warm --> Cold: Disconnect
Hot --> Cooling: Forceful disconnect
Cooling --> Cold: Cooldown elapsed
Cooling sits between Hot/Warm and Cold — a peer whose connection is being torn down but
hasn't fully released yet (the outbound-governor reflection of the connection manager's
TerminatingState, dugite's analogue of TCP TIME_WAIT). It is not eligible for re-promotion
until it reaches Cold.
Target Counts
The governor maintains six independent target counts:
| Target | Default |
|---|---|
| Known peers | 150 |
| Established peers | 30 |
| Active peers | 20 |
| Known big-ledger peers | 15 |
| Established big-ledger peers | 10 |
| Active big-ledger peers | 5 |
When any target is not met, the governor attempts to satisfy the deficit. When any target is exceeded, surplus peers are demoted by lowest reputation.
Local Root Peer Pinning
Local root peers (from localRoots in the topology file) have pinned targets
that override the normal target counts. Local roots are never demoted for
surplus reduction and are never churned.
Churn
The governor performs periodic churn to rotate peers:
- Deadline churn (normal mode) — Approximately every 55 minutes, a fraction of established and active peers are replaced.
- Bulk sync churn — During active block download, churn cycles are more aggressive (~15 minutes) to shed peers with poor block-fetch performance.
Big Ledger Peer Preference During Sync
Big ledger peers (SPOs in the top 90% of stake, obtained via
GetLedgerPeerSnapshot) serve as trusted anchors during bulk block download.
The governor maintains a separate target bucket for BLPs. When SyncState is
Syncing or PreSyncing, BLP targets take priority.
Thread Safety
The PeerManager is wrapped in Arc<RwLock<PeerManager>>. Each governor tick acquires a read
lock to snapshot peer state and compute GovernorActions, then a separate write lock only to
apply the fast, inline actions (background connects are dispatched as their own tasks and never
hold the lock), keeping the write-lock window minimal.
Files
| File | Purpose |
|---|---|
crates/dugite-network/src/peer/governor.rs | Policy decisions and target enforcement |
crates/dugite-network/src/peer/manager.rs | Peer state tracking and reputation |
crates/dugite-network/src/peer/selection.rs | Composite scoring formula |
crates/dugite-network/src/peer/discovery.rs | Peer discovery (topology, DNS, ledger, peer-sharing) |
crates/dugite-node/src/node/mod.rs | Governor tick wiring (inline in the main select! loop) |
crates/dugite-node/src/config.rs | Topology parsing, target defaults |
Ouroboros Genesis Support
Dugite includes a Genesis State Machine (GSM) that tracks the node's sync progression through the Ouroboros Genesis protocol states.
Overview
The GSM implements three states matching the Ouroboros Genesis specification:
- PreSyncing — Waiting for enough trusted big ledger peers (BLPs). The Historical Availability Assumption (HAA) requires a minimum number of active BLPs before sync begins.
- Syncing — Active block download with density-based peer evaluation. The GSM monitors chain density across peers and can disconnect peers with insufficient chain density (GDD).
- CaughtUp — Normal Praos operation. The node is at or near the chain tip and participates in standard consensus.
Enabling Genesis Mode
Genesis mode is opt-in via the --consensus-mode genesis CLI flag:
dugite-node run \
--consensus-mode genesis \
--config config/preview/config.json \
...
When not enabled (the default praos mode), the GSM immediately enters CaughtUp and all Genesis constraints are disabled. This is the recommended mode for nodes that sync from Mithril snapshots.
State Transitions
stateDiagram-v2
[*] --> PreSyncing: no marker, tip stale
[*] --> Syncing: no marker, tip recent (dugite ext.)
[*] --> CaughtUp: marker file present, tip fresh
[*] --> PreSyncing: marker present but tip too old (marker deleted)
PreSyncing --> Syncing: HAA satisfied
Syncing --> CaughtUp: all peers idle + tip fresh
CaughtUp --> PreSyncing: tip becomes stale
A caught_up.marker file is written to the database directory when the node reaches CaughtUp, enabling fast restart without re-evaluating the Genesis bootstrap. The startup state is chosen from the marker's presence and the current tip's age (mirroring Haskell's initializationGsmState):
| Marker | Tip age at startup | Initial state |
|---|---|---|
| Absent | Unknown, or >= the stability-window threshold | PreSyncing |
| Absent | Recent (< threshold) | Syncing — dugite extension (see below) |
| Present | Young enough | CaughtUp |
| Present | Too old | PreSyncing, and the marker is deleted |
The "absent marker + recent tip → Syncing" row does not exist in Haskell, where an absent
marker always starts in PreSyncing. Haskell avoids the resulting stall by requiring a
peerSnapshotFile in the topology so big-ledger peers are seeded instantly; dugite adds this
startup shortcut instead, for deployments (e.g. fresh Mithril-snapshot bootstraps) without a peer
snapshot file — without it, a node that is already near the live tip (its chain already certified
by the Mithril certificate chain) would otherwise wait through a full HAA bootstrap it doesn't
need, and could stall for k blocks in the interim.
The Historical Availability Assumption (HAA) in detail
PreSyncing → Syncing is gated by haa_satisfied(), which is a three-way case split (not a
single "enough BLPs" check), mirroring Haskell's outboundConnectionsState:
- Bootstrap peers configured (topology has a non-empty
bootstrapPeersset) — satisfied when every established outbound peer is inbootstrap ∪ trustable local roots(a closure condition) and at least one of those peers is both hot and specifically a bootstrap peer (not just any trusted hot peer). Inbound connections are excluded from this assessment entirely. - No bootstrap peers, Praos mode — always
false. Haskell treats this as ordinaryUntrustedState, so this is silent (no warning), not an error condition. - No bootstrap peers, Genesis mode — satisfied purely by the count of active (hot) big-ledger peers meeting the configured minimum — untrusted peers don't factor in at all.
Only case 3 is the direct "enough BLPs" gate for a from-scratch Genesis bootstrap; case 1 is what governs a bootstrap-peer-configured topology, and case 2 means Praos-mode nodes without bootstrap peers never satisfy HAA (irrelevant to them, since the GSM only enforces HAA in Genesis mode).
Features
- State tracking: PreSyncing/Syncing/CaughtUp with automatic transitions
- Big Ledger Peer identification: Pools in the top 90% of active stake are classified as BLPs
- Genesis Density Disconnector (GDD): Compares chain density across peers within the genesis window and disconnects peers with insufficient density
- Limit on Eagerness (LoE): Computes the maximum immutable tip slot based on candidate chain tips
- Peer snapshot loading: JSON-based peer snapshot for initial peer discovery
Recommended Deployment
The recommended deployment path uses Mithril snapshot import for fast sync with the default praos consensus mode:
# Import a Mithril snapshot first
dugite-node mithril-import --network-magic 2 --database-path ./db
# Then run in default praos mode
dugite-node run --config config/preview/config.json --database-path ./db ...
Protocol Parameters Reference
Cardano protocol parameters control fees, block sizes, staking mechanics,
script execution budgets, and governance. Every one of them is mutable through
a ParameterChange governance action, so the values below are not constants —
always query a node for truth.
Mainnet values in this page were read from mainnet epoch 646. They are included to give a sense of scale, not as defaults. Re-read them from a node before relying on any of them.
Querying Parameters
dugite-cli query protocol-parameters \
--socket-path ./node.sock \
--out-file protocol-params.json
The JSON key names in the tables below are the cardano-cli-compatible names
that dugite-cli emits and accepts. They are not the Rust field names and
they are not produced by serde — they are written by the N2C client decoder in
crates/dugite-network/src/n2c_client.rs. Where cardano-cli accepts an older
alias, both are listed.
Fee Parameters
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| Min fee coefficient | txFeePerByte / minFeeA | Fee per byte of transaction size | 44 |
| Min fee constant | txFeeFixed / minFeeB | Fixed fee component | 155381 |
| Min UTxO cost per byte | utxoCostPerByte / coinsPerUTxOByte | Minimum lovelace per byte of UTxO (Babbage+) | 4310 |
| Reference script fee | minFeeRefScriptCostPerByte | Tiered fee per byte of reference script (Conway) | 15 |
The base transaction fee formula is:
fee = txFeePerByte * tx_size_in_bytes + txFeeFixed
Conway adds a tiered reference-script surcharge on top, growing geometrically with total reference-script size.
Minimum-UTxO is era-dispatched, not one formula. Dugite selects the per-era Haskell calculation from the protocol version in force (issue #919): flat
minUTxOValueat PV ≤ 3; MaryscaledMinDepositat PV 4; Alonzo(27 + size + dataHashSize) × coinsPerUTxOWordat PV 5–6; Babbage(160 + size) × coinsPerUTxOByteat PV ≥ 7. Key 17 therefore meanscoinsPerUTxOWordbefore Babbage andcoinsPerUTxOBytefrom Babbage on.
Block Size Parameters
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| Max block body size | maxBlockBodySize | Maximum block body size in bytes | 90112 |
| Max transaction size | maxTxSize | Maximum transaction size in bytes | 16384 |
| Max block header size | maxBlockHeaderSize | Maximum block header size in bytes | 1100 |
Staking Parameters
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| Stake address deposit | stakeAddressDeposit / keyDeposit | Deposit for stake key registration (lovelace) | 2000000 |
| Pool deposit | stakePoolDeposit / poolDeposit | Deposit for pool registration (lovelace) | 500000000 |
| Pool retire max epoch | poolRetireMaxEpoch / eMax | Maximum future epochs for pool retirement | 18 |
| Pool target count | stakePoolTargetNum / nOpt | Target number of pools (k parameter) | 500 |
| Min pool cost | minPoolCost | Minimum fixed pool cost (lovelace) | 170000000 |
Monetary Policy
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| Monetary expansion (rho) | monetaryExpansion | Rate of new ADA created from reserves per epoch | 0.003 |
| Treasury cut (tau) | treasuryCut | Fraction of rewards directed to the treasury | 0.20 |
| Pledge influence (a0) | poolPledgeInfluence | How pledge affects reward calculation | 0.3 |
These three are exact rationals on the wire (CBOR tag 30), not floats.
Dugite parses genesis values as exact Scientific and never through an
f64 intermediate — a lossy parse here diverges the reward calculation.
Plutus Execution Parameters
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| Execution unit prices | executionUnitPrices | {priceMemory, priceSteps} rationals | {0.0577, 0.0000721} |
| Max tx execution units | maxTxExecutionUnits | {memory, steps} per transaction | {16500000, 10000000000} |
| Max block execution units | maxBlockExecutionUnits | {memory, steps} per block | {72000000, 20000000000} |
| Max value size | maxValueSize | Maximum serialized value size in bytes | 5000 |
| Collateral percentage | collateralPercentage | Collateral % of total tx fee for Plutus txs | 150 |
| Max collateral inputs | maxCollateralInputs | Maximum collateral inputs per tx | 3 |
| Cost models | costModels | Per-language builtin cost vectors (PlutusV1–PlutusV3) | — |
maxValueSizeis compared with a strict>, against a size computed with HaskellencodeMapsemantics — indefinite-length CBOR map headers above 23 entries, definite at or below. Getting that wrong over-counts by one byte on large asset maps and produces a false Phase-1 rejection (issue #930).
Governance Parameters (Conway)
| Parameter | JSON Key | Description | Mainnet value |
|---|---|---|---|
| DRep deposit | dRepDeposit | Deposit for DRep registration (lovelace) | 500000000 |
| DRep activity | dRepActivity | Epochs of inactivity before a DRep goes dormant | 20 |
| Gov action deposit | govActionDeposit | Deposit for governance action submission (lovelace) | 100000000000 |
| Gov action lifetime | govActionLifetime | Governance action expiry (epochs) | 6 |
| Committee min size | committeeMinSize | Minimum constitutional committee size | 5 |
| Committee max term | committeeMaxTermLength | Maximum committee member term (epochs) | 146 |
Voting Thresholds
The threshold parameters travel on the wire as two fixed-order arrays, not
as individual keys: poolVotingThresholds (5 entries, CBOR key 25) and
drepVotingThresholds (10 entries, CBOR key 26).
poolVotingThresholds — array order:
| Position | JSON Key |
|---|---|
| 0 | pvtMotionNoConfidence |
| 1 | pvtCommitteeNormal |
| 2 | pvtCommitteeNoConfidence |
| 3 | pvtHardForkInitiation |
| 4 | pvtPPSecurityGroup |
drepVotingThresholds — array order:
| Position | JSON Key |
|---|---|
| 0 | dvtMotionNoConfidence |
| 1 | dvtCommitteeNormal |
| 2 | dvtCommitteeNoConfidence |
| 3 | dvtUpdateToConstitution |
| 4 | dvtHardForkInitiation |
| 5 | dvtPPNetworkGroup |
| 6 | dvtPPEconomicGroup |
| 7 | dvtPPTechnicalGroup |
| 8 | dvtPPGovGroup |
| 9 | dvtTreasuryWithdrawal |
Which body votes on what
This is the ratification matrix Dugite implements, matching Haskell
Conway.Rules.Ratify. "—" means that body does not vote on the action at all
(it is not an abstention — the check is simply absent).
| Action type | DRep threshold | SPO threshold | Constitutional Committee |
|---|---|---|---|
| No Confidence | dvtMotionNoConfidence | pvtMotionNoConfidence | — |
| Update Committee (normal) | dvtCommitteeNormal | pvtCommitteeNormal | — |
| Update Committee (under no-confidence) | dvtCommitteeNoConfidence | pvtCommitteeNoConfidence | — |
| New Constitution | dvtUpdateToConstitution | — | votes |
| Hard Fork Initiation | dvtHardForkInitiation | pvtHardForkInitiation | votes |
| Parameter Change | every affected group's dvtPP*Group must pass independently | pvtPPSecurityGroup, only if a security-tagged parameter is touched | votes |
| Treasury Withdrawal | dvtTreasuryWithdrawal | — | votes |
| Info | never ratifies (informational only) | — | — |
Two consequences worth internalising:
- There is no
pvtPPEconomicGroup. The only SPO threshold for parameter changes ispvtPPSecurityGroup, and SPOs are excluded entirely from a parameter change that touches no security-tagged parameter. - A parameter change spanning several groups must clear each group's DRep threshold, not just the highest one.
During the Conway bootstrap phase, DRep thresholds are treated as zero (always met) and only the SPO and committee checks bind.
CBOR keys for a ProtocolParamUpdate
Governance actions carry parameter changes as a sparse CBOR map keyed by integer. This is the complete table Dugite encodes and decodes.
| Key | Parameter | Notes |
|---|---|---|
| 0 | txFeePerByte / minFeeA | |
| 1 | txFeeFixed / minFeeB | |
| 2 | maxBlockBodySize | |
| 3 | maxTxSize | |
| 4 | maxBlockHeaderSize | |
| 5 | stakeAddressDeposit / keyDeposit | |
| 6 | stakePoolDeposit / poolDeposit | |
| 7 | poolRetireMaxEpoch / eMax | |
| 8 | stakePoolTargetNum / nOpt | |
| 9 | poolPledgeInfluence (a0) | CBOR tag 30 rational |
| 10 | monetaryExpansion (rho) | CBOR tag 30 rational |
| 11 | treasuryCut (tau) | CBOR tag 30 rational |
| 12 | decentralization (d) | pre-Conway only — decode only |
| 13 | extraEntropy | pre-Conway only — decode only |
| 14 | protocolVersion | pre-Conway only — [major, minor], decode only |
| 15 | minUTxOValue | pre-Conway only — decode only (restored in #919) |
| 16 | minPoolCost | |
| 17 | coinsPerUTxOWord / utxoCostPerByte | Meaning depends on the PV in force before this update's own PV bump |
| 18 | costModels | map {0: PlutusV1, 1: PlutusV2, 2: PlutusV3, 3: PlutusV4} |
| 19 | executionUnitPrices | [memPrice, stepPrice] |
| 20 | maxTxExecutionUnits | [mem, steps] |
| 21 | maxBlockExecutionUnits | [mem, steps] |
| 22 | maxValueSize | |
| 23 | collateralPercentage | |
| 24 | maxCollateralInputs | |
| 25 | poolVotingThresholds | array of 5 rationals, order above |
| 26 | drepVotingThresholds | array of 10 rationals, order above |
| 27 | committeeMinSize | |
| 28 | committeeMaxTermLength | |
| 29 | govActionLifetime | |
| 30 | govActionDeposit | |
| 31 | dRepDeposit | |
| 32 | dRepActivity | |
| 33 | minFeeRefScriptCostPerByte | CBOR tag 30 rational |
| 34 | maxRefScriptSizePerBlock | Dijkstra — decode only |
| 35 | maxRefScriptSizePerTx | Dijkstra — decode only |
| 36 | refScriptCostStride | Dijkstra — decode only |
| 37 | refScriptCostMultiplier | Dijkstra — decode only |
Notes on asymmetry, because it bites:
- Keys 12–15 exist only in the pre-Conway (Shelley → Babbage) update shape. Dugite decodes them for historical replay; the Conway encoder never emits them, and the Conway decoder skips them.
- Keys 34–37 are the unreleased Dijkstra era. They decode but are not encoded.
- Key 30 is
govActionDepositand key 31 isdRepDeposit— the order is the opposite of what the alphabetical reading suggests, and swapping them silently produces a valid-looking but wrong proposal.
Encoder: crates/dugite-serialization/src/encode/protocol_params.rs.
Decoders: crates/dugite-serialization/src/decode/era_conway.rs (Conway and
later) and .../era_shelley.rs (pre-Conway).
N2C wire encoding of the current parameters
The GetCurrentPParams LocalStateQuery reply is not the sparse
integer-keyed map above. It is a positional CBOR array(31), indices 0–30,
matching Haskell's EncCBOR (ConwayPParams Identity ConwayEra). The two layouts
are different and are not interchangeable.
| Index | Parameter | Index | Parameter |
|---|---|---|---|
| 0 | txFeePerByte | 16 | executionUnitPrices |
| 1 | txFeeFixed | 17 | maxTxExecutionUnits |
| 2 | maxBlockBodySize | 18 | maxBlockExecutionUnits |
| 3 | maxTxSize | 19 | maxValueSize |
| 4 | maxBlockHeaderSize | 20 | collateralPercentage |
| 5 | stakeAddressDeposit | 21 | maxCollateralInputs |
| 6 | stakePoolDeposit | 22 | poolVotingThresholds |
| 7 | poolRetireMaxEpoch | 23 | drepVotingThresholds |
| 8 | stakePoolTargetNum | 24 | committeeMinSize |
| 9 | poolPledgeInfluence | 25 | committeeMaxTermLength |
| 10 | monetaryExpansion | 26 | govActionLifetime |
| 11 | treasuryCut | 27 | govActionDeposit |
| 12 | protocolVersion ([major, minor]) | 28 | dRepDeposit |
| 13 | minPoolCost | 29 | dRepActivity |
| 14 | utxoCostPerByte | 30 | minFeeRefScriptCostPerByte |
| 15 | costModels |
Every index 0–30 is populated; there are no gaps and no Dijkstra slots.
GetGenesisConfig uses a different, legacy Shelley-era layout —
array(18) on N2C v16–v20 and array(17) on v21+.
Encoder: crates/dugite-node/src/node/n2c_query/encoding.rs
(encode_protocol_params_cbor). Client-side decoder to cardano-cli JSON:
crates/dugite-network/src/n2c_client.rs (parse_protocol_params_cbor).
Cardano Mini-Protocol Reference
This document is the definitive implementation reference for every Cardano
mini-protocol used in node-to-node (N2N) and node-to-client (N2C)
communication. It covers the complete state machine, exact CBOR wire format,
timing constraints, flow-control rules, and every protocol-error condition for
each protocol. The protocol descriptions are derived from the Haskell source in
the IntersectMBO/ouroboros-network repository.
Where Dugite's own coverage, negotiated versions, or operational constants differ from the upstream defaults, that is recorded in Dugite Implementation Status below and called out inline in the affected sections.
Connection Model and Multiplexer
All mini-protocols share a single TCP connection per peer, multiplexed by the
network-mux layer using 8-byte big-endian SDU headers:
Bytes Field
----- -----
0-3 transmission_time u32 BE (microseconds, monotonic — used for RTT measurement)
4-5 protocol_and_dir u16 BE (bit 15 = direction, bits 0-14 = protocol number)
6-7 payload_length u16 BE (max 65535)
The direction bit is not a separate flags byte — it is the top bit of the
protocol field, so the protocol number itself is capped at 32767. Bit 15 = 0
means the SDU was sent by the TCP connection initiator, bit 15 = 1 means the
responder. On ingress the bit is flipped: what the remote sends as
InitiatorDir is received as ResponderDir locally.
Dugite implementation: crates/dugite-network/src/mux/segment.rs.
Large messages are fragmented across multiple SDUs transparently. Handshake
(protocol 0) is itself SDU-framed and runs through the mux, not on the raw
socket — Dugite starts the mux first, subscribes the protocol-0 channel, runs
the handshake on it, and only then subscribes the remaining protocol channels
(crates/dugite-node/src/node/peer_connection.rs).
Dugite's SDU payload sizes: 12288 bytes over TCP (N2N) and 32768 bytes over a Unix socket (N2C).
Key invariant: if any single mini-protocol thread throws an exception, the entire mux — and therefore the entire TCP connection — is torn down. Protocol errors are fatal to the connection, not just to the affected mini-protocol.
Sources:
ouroboros-network/network-mux/src/Network/Mux/Codec.hsouroboros-network/network-mux/src/Network/Mux/Types.hsouroboros-network/network-mux/src/Network/Mux/Egress.hs
Shared Encoding Primitives
These types are used identically across all protocols.
Point
A Point identifies a position on the chain by slot and header hash.
; CBOR encoding (Haskell: encodePoint / decodePoint)
point = [] ; Origin — empty definite-length list
/ [slot_no, header_hash] ; At(slot, hash) — definite-length list of 2
slot_no = uint ; word64
header_hash = bstr ; 32 bytes (Blake2b-256 of header)
Source: ouroboros-network/ouroboros-network/api/lib/Ouroboros/Network/Block.hs
Tip
A Tip is the chain tip as seen by the server. It is a (Point, BlockNo) pair.
; N2N ChainSync / N2C LocalChainSync
tip = [slot_no, header_hash, block_no] ; At(pt, blockno)
/ [0] ; TipGenesis (Origin point, blockno=0)
block_no = uint ; word64
Source: ouroboros-network/ouroboros-network/api/lib/Ouroboros/Network/Block.hs
(encodeTip / decodeTip)
Byte and Time Limit Constants
These constants appear in state-machine timeout and size-limit tables throughout this document.
| Constant | Value | Source in Codec/Limits.hs |
|---|---|---|
smallByteLimit | 65535 bytes | Protocol/Limits.hs:smallByteLimit |
largeByteLimit | 2 500 000 bytes | Protocol/Limits.hs:largeByteLimit |
shortWait | 10 seconds | Protocol/Limits.hs:shortWait |
longWait | 60 seconds | Protocol/Limits.hs:longWait |
waitForever | no timeout | Protocol/Limits.hs:waitForever (= Nothing) |
Source: ouroboros-network/ouroboros-network/api/lib/Ouroboros/Network/Protocol/Limits.hs
N2N Mini-Protocol IDs
| Protocol | ID |
|---|---|
| Handshake | 0 |
| DeltaQ | 1 (reserved, never used) |
| ChainSync | 2 |
| BlockFetch | 3 |
| TxSubmission2 | 4 |
| KeepAlive | 8 |
| PeerSharing | 10 |
| Peras Cert | 16 (spec only — not implemented by Dugite) |
| Peras Vote | 17 (spec only — not implemented by Dugite) |
Protocol 1 is reserved (historically DeltaQ) and never carries traffic. Dugite silently discards any inbound SDU on protocol 1 rather than treating it as a protocol error.
N2C Mini-Protocol IDs
| Protocol | ID |
|---|---|
| Handshake | 0 |
| LocalChainSync | 5 |
| LocalTxSubmission | 6 |
| LocalStateQuery | 7 |
| LocalTxMonitor | 9 |
Protocol ID constants: crates/dugite-network/src/protocol/mod.rs.
Dugite Implementation Status
Everything below in this page describes the protocol as specified by
IntersectMBO/ouroboros-network. This section records what Dugite actually
implements, and where its own constants differ from the Haskell defaults.
Coverage
| Protocol | Client (initiator) | Server (responder) |
|---|---|---|
| N2N Handshake (0) | yes | yes |
| N2N ChainSync (2) | yes | yes |
| N2N BlockFetch (3) | yes | yes |
| N2N TxSubmission2 (4) | yes | yes |
| N2N KeepAlive (8) | yes | yes |
| N2N PeerSharing (10) | yes | yes |
| N2C Handshake (0) | yes | yes |
| N2C LocalChainSync (5) | no state machine — raw channel only | yes |
| N2C LocalTxSubmission (6) | yes (submit_tx) | yes |
| N2C LocalStateQuery (7) | yes | yes |
| N2C LocalTxMonitor (9) | yes | yes |
All ten N2N channels (five protocols × initiator + responder) are subscribed on
every connection, inbound and outbound. Negotiating InitiatorOnly does not
disable Dugite's responder side.
The N2N ChainSync client used in production is not the library's
PipelinedChainSyncClient — the node runs its own pipelined state machine in
crates/dugite-node/src/node/sync.rs over the same codec. The library client is
exported and tested, but not on the sync path.
Handshake versions actually offered
| Versions | Preference order | Version data | |
|---|---|---|---|
| N2N | 14, 15 | [15, 14] | array(4): [networkMagic, initiatorOnly, peerSharing, query] |
| N2C | 16–23 (wire 0x8000 | v, i.e. 32784–32791) | [23 … 16] | array(2): [networkMagic, query] |
Negotiation rules as implemented: network magic must match exactly;
initiatorOnly is OR'd (either side asking degrades both); peerSharing is
AND'd (both must enable); query is OR'd. Unknown version numbers in a peer's
proposal map are skipped rather than rejected, so an older cardano-node
offering v13 still negotiates. The proposal map is emitted with keys in
ascending order for canonical CBOR, capped at 32 entries. Handshake timeout is
10 s on both roles.
Implementation: crates/dugite-network/src/handshake/{n2n.rs,n2c.rs,mod.rs}.
Dugite's own limits and timeouts
These are Dugite's operational values. Where the Haskell default differs, the per-protocol sections below give the upstream number.
| Setting | Value | Where |
|---|---|---|
| ChainSync pipeline depth | DUGITE_PIPELINE_DEPTH, default 300, low mark ⅔ (~200); collapses toward 1 near the tip | node/sync.rs |
ChainSync StMustReply timeout | uniform random per connection in [601 s, 911 s] | protocol/chainsync/serve_core.rs |
| ChainSync max intersect points | 100 | protocol/chainsync/mod.rs |
| BlockFetch batch | 2000 blocks per range; 2 500 000 B per MsgBlock; range span ≤ 432 000 slots | protocol/blockfetch/ |
| BlockFetch in-flight ranges | 100 per peer | protocol/blockfetch/decision.rs |
| TxSubmission2 | 10 txids per request, maxUnacked 100, 1000 in-flight txids | protocol/txsubmission/server.rs |
| KeepAlive | cookie u16 randomly seeded per connection; ping every 10 s; pong timeout 30 s; 3 consecutive misses closes the connection; server accepts at most 144 000 pings/session | protocol/keepalive/ |
| PeerSharing | requests 8 peers per round, max 2 requests in flight globally; decode cap 255 addresses; non-routable addresses (RFC1918, CGNAT 100.64/10, loopback, link-local, IPv6 ULA) filtered on both sides | protocol/peersharing/, node/connection_lifecycle.rs |
| Mux SDU payload read timeout | 30 s after a header arrives (header wait is deliberately unbounded) | mux/ingress.rs |
| Inbound connection idle timeout | 300 s | connection/manager.rs |
| Ingress byte limits | default 4 MB; ChainSync 512 KB; BlockFetch 48 MB; TxSubmission 8 MB; N2C channels 1 MB | node/peer_connection.rs |
| Global CBOR nesting depth cap | 64 | codec.rs |
| LocalStateQuery query blob cap | 4 KB | protocol/local_state_query/server.rs |
Protocol Temperatures (N2N)
Protocol temperature determines when each N2N mini-protocol is started during
the peer lifecycle (cold → warm → hot).
| Temperature | Protocols | Started when |
|---|---|---|
| Established | KeepAlive (8), PeerSharing (10) | On cold→warm promotion |
| Warm | (none currently) | — |
| Hot | ChainSync (2), BlockFetch (3), TxSubmission2 (4) | On warm→hot promotion |
Hot protocols use StartOnDemand for the responder side (they wait for the
first inbound byte). Initiator sides are started eagerly by startProtocols.
Source: ouroboros-network/cardano-diffusion/lib/Cardano/Network/Diffusion/Peer/
N2N Protocol 0: Handshake
Identity
- Protocol ID: 0 (runs on raw socket bearer before mux starts)
- Direction: Initiator sends
MsgProposeVersions, responder replies - Versions: V14 (Plomin HF, mandatory since 2025-01-29), V15 (SRV DNS)
State Machine
StPropose (ClientAgency) -- initiator has agency
│
│ MsgProposeVersions
▼
StConfirm (ServerAgency) -- server chooses version
│
├─── MsgAcceptVersion ──→ StDone
├─── MsgRefuse ──→ StDone
└─── MsgQueryReply ──→ StDone
| State | Agency | Meaning |
|---|---|---|
StPropose | Client | Initiator must send its version list |
StConfirm | Server | Server must accept, refuse, or query |
StDone | Nobody | Terminal |
Terminal state: StDone — connection is closed after handshake completes (for N2N; the mux then starts).
Wire Format
Source: ouroboros-network/ouroboros-network/framework/lib/Ouroboros/Network/Protocol/Handshake/Codec.hs
and cardano-diffusion/protocols/cddl/specs/handshake-node-to-node-v14.cddl
; Every handshake message is a definite-length CBOR array.
MsgProposeVersions = [0, versionTable]
MsgAcceptVersion = [1, versionNumber, versionData]
MsgRefuse = [2, refuseReason]
MsgQueryReply = [3, versionTable]
; versionTable is a CBOR definite-length MAP (not an array).
; Keys are encoded in ascending order.
versionTable = { * versionNumber => versionData }
; N2N version numbers implemented by Dugite (V14=14, V15=15)
; Note: N2N does NOT set bit-15. Only N2C uses bit-15.
; V16 (adds perasSupport) is defined in the Peras spec but not yet implemented.
versionNumber = 14 / 15
; Version data for V14/V15: 4-element array
versionData_v14 = [networkMagic, initiatorOnly, peerSharing, query]
networkMagic = uint .size 4 ; word32 (mainnet=764824073, preview=2, preprod=1)
initiatorOnly = bool ; true=InitiatorOnly, false=InitiatorAndResponder
peerSharing = 0 / 1 ; 0=Disabled, 1=Enabled
query = bool
refuseReason
= [0, [* versionNumber]] ; VersionMismatch
/ [1, versionNumber, tstr] ; HandshakeDecodeError
/ [2, versionNumber, tstr] ; Refused
Version Negotiation Rules
Source: cardano-diffusion/api/lib/Cardano/Network/NodeToNode/Version.hs
- The responder picks the highest version number that appears in both the initiator's and responder's version tables.
- If no common version:
MsgRefusewithVersionMismatch. networkMagicmust match exactly; mismatch →MsgRefusewithRefused.initiatorOnlyDiffusionMode=min(local, remote)— more restrictive wins (i.e.,InitiatorOnlyif either side is).peerSharing=local <> remote(Semigroup): both must be Enabled for Enabled; any Disabled results in Disabled.InitiatorOnlynodes automatically have Disabled.query=local || remote(logical OR).
MsgQueryReply Semantics
When the initiator sends MsgProposeVersions with query=true, the responder
must reply with MsgQueryReply (a copy of its own version table) and then
close the connection. This is used by cardano-cli for version probing. The mux
never starts in this case.
Timeout
Handshake SDU read/write: 10 seconds per SDU. There is no per-state timeout beyond this; the handshake exchange must complete within one SDU read cycle on each side.
N2N Protocol 2: ChainSync
Identity
- Protocol ID: 2
- Temperature: Hot (started on warm→hot promotion)
- Direction: N2N ChainSync streams block headers only (not full blocks). Full blocks are fetched via BlockFetch.
- Versions: All N2N versions (V7+ upstream; Dugite offers V14/V15)
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/Type.hs
StIdle (ClientAgency) -- client requests next update or intersect
│
├─── MsgRequestNext ──→ StNext(StCanAwait)
├─── MsgFindIntersect ──→ StIntersect
└─── MsgDone ──→ StDone
StNext(StCanAwait) (ServerAgency) -- server can immediately reply or defer
│
├─── MsgAwaitReply ──→ StNext(StMustReply)
├─── MsgRollForward ──→ StIdle
└─── MsgRollBackward ──→ StIdle
StNext(StMustReply) (ServerAgency) -- server MUST reply (already sent await)
│
├─── MsgRollForward ──→ StIdle
└─── MsgRollBackward ──→ StIdle
StIntersect (ServerAgency) -- server searching for intersection
│
├─── MsgIntersectFound ──→ StIdle
└─── MsgIntersectNotFound ─→ StIdle
StDone (NobodyAgency)
Critical invariant: MsgAwaitReply is only valid in state StNext(StCanAwait).
The server transitions to StNext(StMustReply) after sending it. Sending
MsgAwaitReply when the client sent a non-blocking variant (Pipeline rather
than Request) or when the server has already sent MsgAwaitReply this round
is a protocol error (ProtocolErrorRequestNonBlocking). The typed-protocol
framework enforces this at compile time; a Rust implementation must enforce it
at runtime by tracking which sub-state of StNext is current.
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/Codec.hs
and cardano-diffusion/protocols/cddl/specs/chain-sync.cddl
MsgRequestNext = [0]
MsgAwaitReply = [1]
MsgRollForward = [2, header, tip]
MsgRollBackward = [3, point, tip]
MsgFindIntersect = [4, points]
MsgIntersectFound = [5, point, tip]
MsgIntersectNotFound = [6, tip]
MsgDone = [7]
; points is a DEFINITE-length array (not indefinite)
points = [* point]
N2N header encoding in MsgRollForward: For the CardanoBlock HFC block
type, the header is wrapped as:
header = [era_index, serialised_header_bytes]
where era_index is 0=Byron, 1=Shelley, ..., 6=Conway, 7=Dijkstra (see
TxSubmission2 section for full table), and serialised_header_bytes is
tag(24)(bstr(cbor_encoded_header)) — CBOR-in-CBOR wrapping via
wrapCBORinCBOR.
Source: ouroboros-consensus/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Serialisation.hs
Pipelining
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/PipelineDecision.hs
ChainSync uses the pipelineDecisionLowHighMark strategy with default marks
lowMark=200, highMark=300 (Dugite uses configurable depth via
DUGITE_PIPELINE_DEPTH, default 300).
pipelineDecisionLowHighMark :: Word16 -> Word16 -> MkPipelineDecision
Decision logic (given n outstanding requests, clientTip, serverTip):
n=0, clientTip == serverTip→Request(non-pipelined, triggers await semantics)n=0, clientTip < serverTip→Pipelinen>0, clientTip + n >= serverTip→Collect(we're caught up, stop pipelining)n >= highMark→Collect(high-water: drain before adding more)n < lowMark→CollectOrPipeline(can collect or pipeline)n >= lowMark→Collect(above low mark in high state)
When n=0 and clientTip == serverTip: the client sends a non-pipelined
Request, the server is at its tip and sends MsgAwaitReply (valid because
the client sent a blocking request). This is the "at tip" steady state.
Timing
Source: ouroboros-network/cardano-diffusion/protocols/lib/Cardano/Network/Protocol/ChainSync/Codec/TimeLimits.hs
| State | Trusted peer | Untrusted peer |
|---|---|---|
StIdle | 3373 s | 3373 s (configurable via ChainSyncIdleTimeout) |
StNext(StCanAwait) | 10 s (shortWait) | 10 s |
StNext(StMustReply) | waitForever | uniform random 601–911 s |
StIntersect | 10 s | 10 s |
The random range for untrusted StMustReply corresponds to streak-of-empty-slots
probabilities between 99.9% and 99.9999% at f=0.05.
Default ChainSyncIdleTimeout = 3373 seconds.
Source: cardano-diffusion/lib/Cardano/Network/Diffusion/Configuration.hs:defaultChainSyncIdleTimeout
Ingress Queue Limit
highMark × 1400 bytes × 1.1 safety factor
With highMark=300: approximately 462 000 bytes.
Dugite enforces a flat 512 KB ingress byte limit on the ChainSync channel
(crates/dugite-node/src/node/peer_connection.rs).
N2N Protocol 3: BlockFetch
Identity
- Protocol ID: 3
- Temperature: Hot
- Purpose: Bulk download of full block bodies, driven by the BlockFetch decision logic after ChainSync supplies candidate chain headers.
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/BlockFetch/Type.hs
BFIdle (ClientAgency) -- client decides what to fetch
│
├─── MsgRequestRange ──→ BFBusy
└─── MsgClientDone ──→ BFDone
BFBusy (ServerAgency) -- server preparing batch
│
├─── MsgStartBatch ──→ BFStreaming
└─── MsgNoBlocks ──→ BFIdle
BFStreaming (ServerAgency) -- server streaming blocks
│
├─── MsgBlock ──→ BFStreaming (self-loop, one block per message)
└─── MsgBatchDone ──→ BFIdle
BFDone (NobodyAgency)
| State | Agency |
|---|---|
BFIdle | Client |
BFBusy | Server |
BFStreaming | Server |
BFDone | Nobody |
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/BlockFetch/Codec.hs
and cardano-diffusion/protocols/cddl/specs/block-fetch.cddl
MsgRequestRange = [0, lower_point, upper_point]
MsgClientDone = [1]
MsgStartBatch = [2]
MsgNoBlocks = [3]
MsgBlock = [4, block]
MsgBatchDone = [5]
MsgRequestRange: Both lower_point and upper_point are inclusive
(the range spans from lower to upper, both included). Each point uses the
standard point encoding ([] for Origin, [slot, hash] for specific).
Block encoding in MsgBlock: For CardanoBlock, the block is encoded as:
block = [era_index, tag(24)(bstr(cbor_encoded_block))]
The full block (including header and body) is CBOR-serialized, then wrapped in
tag(24)(bytes(cbor_bytes)) (CBOR-in-CBOR), then placed in a 2-element array
with the HFC era index.
Source: ouroboros-consensus/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Serialisation.hs
Timing
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/BlockFetch/Codec.hs:timeLimitsBlockFetch
| State | Timeout |
|---|---|
BFIdle | waitForever |
BFBusy | 60 s (longWait) |
BFStreaming | 60 s (longWait) |
Byte Limits
| State | Limit |
|---|---|
BFIdle | 65535 bytes (smallByteLimit) |
BFBusy | 65535 bytes (smallByteLimit) |
BFStreaming | 2 500 000 bytes (largeByteLimit) |
BlockFetch Decision Loop
The blockFetchLogic thread runs continuously, waking every 10 ms (Praos) or
40 ms (Genesis). It reads candidate chains from ChainSync via STM, computes
which block ranges need to be fetched, and issues MsgRequestRange messages.
| Parameter | Default | Source |
|---|---|---|
maxInFlightReqsPerPeer | 100 | blockFetchPipeliningMax |
maxConcurrencyBulkSync | 1 peer | bfcMaxConcurrencyBulkSync |
maxConcurrencyDeadline | 1 peer | bfcMaxConcurrencyDeadline |
| Decision loop interval (Praos) | 10 ms | bfcDecisionLoopIntervalPraos |
| Decision loop interval (Genesis) | 40 ms | bfcDecisionLoopIntervalGenesis |
Source: cardano-diffusion/lib/Cardano/Network/Diffusion/Configuration.hs:defaultBlockFetchConfiguration
Ingress Queue Limit
max(10 × 2 097 154, 100 × 90 112) × 1.1 ≈ 22 MB.
N2N Protocol 4: TxSubmission2
Identity
- Protocol ID: 4
- Temperature: Hot
- Direction: Inverted agency — the server (inbound/receiver) has agency first. The server requests transactions; the client replies with them. This is the opposite of most protocols.
- Versions: All N2N versions. V2 logic (multi-peer decision loop) is
enabled server-side when
TxSubmissionLogicV2is configured; V1 is the current default in cardano-node.
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/TxSubmission2/Type.hs
StInit (ClientAgency) -- client must send MsgInit before anything else
│
│ MsgInit
▼
StIdle (ServerAgency) -- server has agency; requests txids or terminates
│
├─── MsgRequestTxIds(blocking=true) ──→ StTxIds(StBlocking)
├─── MsgRequestTxIds(blocking=false) ──→ StTxIds(StNonBlocking)
├─── MsgRequestTxs ──→ StTxs
└─── MsgDone ──→ StDone
StTxIds(StBlocking) (ClientAgency) -- client MUST reply, no timeout
│
└─── MsgReplyTxIds(NonEmpty list) ──→ StIdle
(BlockingReply: list must be non-empty)
StTxIds(StNonBlocking) (ClientAgency) -- client must reply within shortWait
│
└─── MsgReplyTxIds(possibly empty) ──→ StIdle
StTxs (ClientAgency) -- client must reply with requested tx bodies
│
└─── MsgReplyTxs(tx list) ──→ StIdle
StDone (NobodyAgency)
MsgDone constraint: MsgDone can only be sent from StIdle (server side).
It is the server's prerogative to terminate, not the client's.
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/TxSubmission2/Codec.hs:encodeTxSubmission2
and cardano-diffusion/protocols/cddl/specs/tx-submission2.cddl
MsgInit = [6]
MsgRequestTxIds = [0, blocking:bool, ack:word16, req:word16]
; blocking=true → StTxIds(StBlocking)
; blocking=false → StTxIds(StNonBlocking)
MsgReplyTxIds = [1, [_ *[txid, size:word32] ]]
; INDEFINITE-length outer list (encodeListLenIndef)
; Each inner entry is a DEFINITE-length array(2)
MsgRequestTxs = [2, [_ *txid ]]
; INDEFINITE-length list
MsgReplyTxs = [3, [_ *tx ]]
; INDEFINITE-length list
MsgDone = [4]
IMPORTANT: Both MsgReplyTxIds, MsgRequestTxs, and MsgReplyTxs use
indefinite-length CBOR arrays (encoded with encodeListLenIndef and
terminated with encodeBreak). The codec explicitly requires this. Using
definite-length arrays is a decoding error.
HFC era-tag wrapping for txids and txs:
For the Cardano HFC instantiation, each txid and each tx is wrapped with
the era index before being placed into the list. The wrapping is done by
encodeNS in ouroboros-consensus:
; txid (GenTxId) encoding
txid = [era_index:uint8, bstr(32)]
; era_index: 0=Byron, 1=Shelley, 2=Allegra, 3=Mary, 4=Alonzo,
; 5=Babbage, 6=Conway, 7=Dijkstra
; payload: 32 raw bytes = Blake2b-256 hash of tx body (no CBOR tag)
; tx (GenTx) encoding
tx = [era_index:uint8, tag(24)(bstr(cbor_of_tx))]
; The transaction CBOR bytes are wrapped in CBOR tag 24 (embedded CBOR)
Example for Conway (era_index=6):
txid = [6, bstr(32_bytes_of_txhash)]
tx = [6, #6.24(bstr(cbor_bytes_of_transaction))]
Source: ouroboros-consensus/ouroboros-consensus-diffusion/src/.../Consensus/Network/NodeToNode.hs
and ouroboros-consensus/src/.../HardFork/Combinator/Serialisation/Common.hs:encodeNS
MsgReplyTxIds — Size Reporting
Each entry in MsgReplyTxIds carries a SizeInBytes (word32) alongside the
txid. This size must include the full HFC envelope overhead that the tx will
have in MsgReplyTxs. For Conway: 3 bytes overhead (1 byte array-of-2 header,
1 byte era_index word8, CBOR tag 24 header). Mismatches beyond the tolerance
threshold (const_MAX_TX_SIZE_DISCREPANCY = 10 bytes in V2 inbound) terminate
the connection.
Blocking vs Non-Blocking Rules
In blocking mode (MsgRequestTxIds(blocking=true)):
req_countmust be >= 1MsgReplyTxIdsreply must contain a non-empty list (BlockingReply)- No timeout: the client MAY block indefinitely in STM waiting for new mempool entries
In non-blocking mode (MsgRequestTxIds(blocking=false)):
- At least one of
ack_countorreq_countmust be non-zero MsgReplyTxIdsreply may be empty (NonBlockingReply [])- Timeout:
shortWait(10 seconds)
Acknowledgment semantics: ack_count tells the client how many previously
announced txids can now be removed from the outbound window. The client
maintains a FIFO of unacknowledgedTxIds. When the server sends
MsgRequestTxIds(ack=N, req=M), the client drops the first N entries from
the FIFO and adds up to M new txids from the mempool.
Timing
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/TxSubmission2/Codec.hs:timeLimitsTxSubmission2
| State | Timeout |
|---|---|
StInit | waitForever |
StIdle | waitForever |
StTxIds(StBlocking) | waitForever |
StTxIds(StNonBlocking) | 10 s (shortWait) |
StTxs | 10 s (shortWait) |
V1 Server Constants (current default)
| Parameter | Value |
|---|---|
maxTxIdsToRequest | 3 |
maxTxToRequest | 2 |
maxUnacknowledgedTxIds | 100 |
txSubmissionInitDelay | 60 s |
The 60-second init delay is applied via threadDelay before the V1 server makes
its first MsgRequestTxIds. This intentionally avoids requesting transactions
during initial chain sync.
V2 Server Constants (experimental)
| Parameter | Value |
|---|---|
maxNumTxIdsToRequest | 12 |
maxUnacknowledgedTxIds | 100 |
txsSizeInflightPerPeer | 6 × 65540 bytes |
txInflightMultiplicity | 2 |
| Decision loop delay | 5 ms |
Source: ouroboros-network/ouroboros-network/lib/Ouroboros/Network/TxSubmission/Inbound/V2/
MsgInit Requirement
MsgInit (tag=6, one-element array [6]) must be the very first message
sent by the client (outbound side) after the mux connection is established for
the TxSubmission2 protocol. The server waits for MsgInit in StInit before
transitioning to StIdle. Sending any other message first is a protocol error.
Ingress Queue Limit
maxUnacknowledgedTxIds × (44 + 65536) × 1.1
With maxUnacknowledgedTxIds=100: approximately 6 666 400 bytes.
N2N Protocol 8: KeepAlive
Identity
- Protocol ID: 8
- Temperature: Established (started on cold→warm, runs for entire connection lifetime)
- Purpose: Detects connection failure and measures round-trip time for GSV (Good-Spread-Variable) calculations used in BlockFetch prioritization.
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/KeepAlive/Type.hs
StClient (ClientAgency) -- client sends keep-alive request
│
├─── MsgKeepAlive(cookie) ──→ StServer
└─── MsgDone ──→ StDone
StServer (ServerAgency) -- server must respond with same cookie
│
└─── MsgKeepAliveResponse(cookie) ──→ StClient
StDone (NobodyAgency)
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/KeepAlive/Codec.hs:codecKeepAlive_v2
MsgKeepAlive = [0, cookie:word16]
MsgKeepAliveResponse = [1, cookie:word16]
MsgDone = [2]
Cookie matching: The server must echo back the exact cookie value sent by
the client. A mismatch raises KeepAliveCookieMissmatch (note: the Haskell
source has the typo "Missmatch" with double-s), which terminates the connection.
Timing
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/KeepAlive/Codec.hs:timeLimitsKeepAlive
| State | Timeout |
|---|---|
StClient | 97 seconds |
StServer | 60 seconds |
The asymmetry is intentional: the client side (97 s) is how long the client
waits before sending the next keep-alive; the server side (60 s) is how long
the server has to respond. The comment in source notes that StServer timeout
"should be 10s" (issue #2505) but is currently 60 s.
Byte Limits
Both states: smallByteLimit (65535 bytes).
Protocol Error Condition
KeepAliveCookieMissmatch oldCookie receivedCookie — thrown when
MsgKeepAliveResponse cookie does not match the outstanding request cookie.
This terminates the connection.
N2N Protocol 10: PeerSharing
Identity
- Protocol ID: 10
- Temperature: Established (started on cold→warm)
- Purpose: Exchange of peer addresses to assist in peer discovery. Only
active when both sides negotiated
peerSharing=1in Handshake.
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/PeerSharing/Type.hs
StIdle (ClientAgency) -- client requests peer addresses or terminates
│
├─── MsgShareRequest(amount) ──→ StBusy
└─── MsgDone ──→ StDone
StBusy (ServerAgency) -- server must reply with peer list
│
└─── MsgSharePeers(addrs) ──→ StIdle
StDone (NobodyAgency)
Wire Format
Source: ouroboros-network/cardano-diffusion/protocols/lib/Cardano/Network/Protocol/PeerSharing/Codec.hs
and cardano-diffusion/protocols/cddl/specs/peer-sharing-v14.cddl
MsgShareRequest = [0, amount:word8]
MsgSharePeers = [1, [* peerAddress]]
MsgDone = [2]
; Peer address encoding (SockAddr)
peerAddress = [0, ipv4:word32, port:word16]
; IPv4: single u32 in network byte order, then port as word16
/ [1, word32, word32, word32, word32, port:word16]
; IPv6: four u32s (network byte order), then port as word16
Protocol error condition: If the server replies with more addresses than
amount requested, it is a protocol error. The client must request no more
than 255 peers (word8 max).
Timing
| State | Timeout |
|---|---|
StIdle | waitForever |
StBusy | 60 s (longWait) |
Server Address Selection Policy
The server only shares addresses for peers that satisfy all of:
knownPeerAdvertise = DoAdvertisePeerknownSuccessfulConnection = TrueknownPeerFailCount = 0
Addresses are randomized using a hash with a salt that rotates every 823 seconds to prevent fingerprinting.
Source: ouroboros-network/ouroboros-network/api/lib/Ouroboros/Network/PeerSelection/PeerSharing/Codec.hs
and ouroboros-network/ouroboros-network/lib/Ouroboros/Network/PeerSharing.hs
Key Policy Constants
| Constant | Value |
|---|---|
policyMaxInProgressPeerShareReqs | 2 |
policyPeerShareRetryTime | 900 s |
policyPeerShareBatchWaitTime | 3 s |
policyPeerShareOverallTimeout | 10 s |
policyPeerShareActivationDelay | 300 s |
ps_POLICY_PEER_SHARE_STICKY_TIME | 823 s (salt rotation) |
ps_POLICY_PEER_SHARE_MAX_PEERS | 10 |
Source: ouroboros-network/ouroboros-network/lib/Ouroboros/Network/Diffusion/Policies.hs
N2C Protocol 0: Handshake (Node-to-Client)
Identity
- Protocol ID: 0 (same as N2N, runs on raw socket before mux)
- Direction: Same as N2N: client proposes, server accepts or refuses
- Versions: V16 (=32784) through V23 (=32791)
Wire Format
Source: CDDL: cardano-diffusion/protocols/cddl/specs/handshake-node-to-client.cddl
Codec: same codecHandshake function as N2N, parameterized on version number type.
; Messages are identical in structure to N2N handshake
MsgProposeVersions = [0, versionTable]
MsgAcceptVersion = [1, versionNumber, nodeToClientVersionData]
MsgRefuse = [2, refuseReason]
MsgQueryReply = [3, versionTable]
; N2C version numbers have bit 15 set to distinguish from N2N
; V16=32784, V17=32785, V18=32786, V19=32787,
; V20=32788, V21=32789, V22=32790, V23=32791
versionNumber = 32784 / 32785 / 32786 / 32787 / 32788 / 32789 / 32790 / 32791
; Encoding: versionNumber_wire = logical_version | 0x8000
; Decoding: logical_version = wire_value & 0x7FFF (after verifying bit 15 is set)
; Version data (V16+): 2-element array
nodeToClientVersionData = [networkMagic:uint, query:bool]
The versionTable in MsgProposeVersions is a definite-length CBOR map
with entries sorted in ascending key order.
Version Features
| N2C Version | Wire Value | What Changed |
|---|---|---|
| V16 | 32784 | Conway era; ImmutableTip acquire; GetStakeDelegDeposits |
| V17 | 32785 | GetProposals, GetRatifyState |
| V18 | 32786 | GetFuturePParams |
| V19 | 32787 | GetBigLedgerPeerSnapshot |
| V20 | 32788 | QueryStakePoolDefaultVote; MsgGetMeasures in LocalTxMonitor |
| V21 | 32789 | New ProtVer codec for Shelley-Babbage; GetPoolDistr2, GetStakeDistribution2, GetMaxMajorProtVersion |
| V22 | 32790 | SRV records in GetBigLedgerPeerSnapshot |
| V23 | 32791 | GetDRepDelegations; LedgerPeerSnapshot includes block hash + NetworkMagic |
Source: cardano-diffusion/api/lib/Cardano/Network/NodeToClient/Version.hs
Version Negotiation
Same rules as N2N:
- Highest common version wins.
networkMagicmust match.query = local || remote(logical OR).- No
initiatorOnlyDiffusionModeorpeerSharingfields in N2C version data.
N2C Protocol 5: LocalChainSync
Identity
- Protocol ID: 5
- Direction: N2C clients receive full serialized blocks (not just headers). This is the key difference from N2N ChainSync.
- Versions: All N2C versions
State Machine
Identical state machine to N2N ChainSync (same Type.hs). See that section for the complete state machine diagram.
Wire Format
Messages tags are identical to N2N ChainSync (0–7). The key difference is the
content of MsgRollForward.
N2C MsgRollForward block encoding:
; N2C LocalChainSync block payload in MsgRollForward
block = [era_id:uint, tag(24)(bstr(cbor_of_full_block))]
The entire block (header + body) is CBOR-encoded, wrapped in CBOR tag(24)
(embedded CBOR), and then paired with the era index in a 2-element array.
Era indices: same as TxSubmission2 (0=Byron through 7=Dijkstra).
This matches the same HFC wrapping used by BlockFetch MsgBlock in N2N.
Differences from N2N ChainSync
| Aspect | N2N ChainSync | N2C LocalChainSync |
|---|---|---|
| Payload type | Block headers only | Full blocks |
| Purpose | Chain selection | Wallet / tool consumption |
| Pipelining | Yes (pipelineDecisionLowHighMark) | Typically none |
| Source of blocks | Server → client | Server → client |
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/Codec.hs (same codec)
N2C Protocol 6: LocalTxSubmission
Identity
- Protocol ID: 6
- Direction: Client submits a single transaction; server accepts or rejects.
- No HFC era-tag wrapping: Unlike N2N TxSubmission2, N2C LocalTxSubmission sends raw transaction CBOR without any HFC era-index prefix.
- Versions: All N2C versions
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxSubmission/Type.hs
StIdle (ClientAgency) -- client submits a transaction or terminates
│
├─── MsgSubmitTx(tx) ──→ StBusy
└─── MsgDone ──→ StDone
StBusy (ServerAgency) -- server validates and responds
│
├─── MsgAcceptTx ──→ StIdle
└─── MsgRejectTx ──→ StIdle
StDone (NobodyAgency)
Blocking semantics: After sending MsgSubmitTx, the client must wait
for MsgAcceptTx or MsgRejectTx before sending another transaction. This
protocol processes one transaction at a time. This is intentional: N2C is
only used by local trusted clients (wallets, CLI), so throughput is not a
concern.
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxSubmission/Codec.hs:encodeLocalTxSubmission
and cardano-diffusion/protocols/cddl/specs/local-tx-submission.cddl
MsgSubmitTx = [0, tx]
MsgAcceptTx = [1]
MsgRejectTx = [2, rejectReason]
MsgDone = [3]
Transaction encoding (tx): Raw transaction CBOR, exactly as produced
by toCBOR on the ledger's Tx type. No HFC wrapper, no era tag, no
tag(24). The server determines the era from the ledger state.
Rejection reason (rejectReason): The full ApplyTxError encoded via
the ledger's EncCBOR instance. For Conway, this is a nested structure of
ConwayLedgerPredFailure variants. The exact encoding is era-specific and
defined in cardano-ledger.
Source: cardano-ledger/eras/conway/impl/src/Cardano/Ledger/Conway/Rules/
N2C Protocol 7: LocalStateQuery
Identity
- Protocol ID: 7
- Direction: Client acquires a ledger state snapshot and submits queries; server responds with query results.
- Versions: All N2C versions. Some queries require specific minimum versions (see Shelley query tag table).
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalStateQuery/Type.hs
StIdle (ClientAgency) -- client acquires a state or terminates
│
├─── MsgAcquire(target) ──→ StAcquiring
└─── MsgDone ──→ StDone
StAcquiring (ServerAgency) -- server acquiring the requested state
│
├─── MsgAcquired ──→ StAcquired
└─── MsgFailure(reason) ──→ StIdle
StAcquired (ClientAgency) -- client can query or release
│
├─── MsgQuery(query) ──→ StQuerying
├─── MsgRelease ──→ StIdle
└─── MsgReAcquire(target)──→ StAcquiring
StQuerying (ServerAgency) -- server computing query result
│
└─── MsgResult(result) ──→ StAcquired
StDone (NobodyAgency)
Re-acquire: MsgReAcquire transitions from StAcquired directly back to
StAcquiring, allowing the client to acquire a new state without going through
StIdle. This avoids a round trip.
Acquire Targets
Three targets exist for MsgAcquire and MsgReAcquire:
| Target | CBOR | Semantics | Min Version |
|---|---|---|---|
SpecificPoint | [0, point] | Acquire the state at a specific slot/hash point | V8+ (any) |
VolatileTip | [8] | Acquire the current tip of the volatile chain | V8+ |
ImmutableTip | [10] | Acquire the tip of the immutable chain | N2C V16+ |
For MsgReAcquire: tags are shifted by 3 → SpecificPoint=[6, point],
VolatileTip=[9], ImmutableTip=[11] (V16+).
VolatileTip and ImmutableTip cannot fail (they always succeed with
MsgAcquired). SpecificPoint can fail if the point is not in the volatile
chain window (yields MsgFailure).
Acquire Failure Codes
AcquireFailurePointTooOld = 0
AcquireFailurePointNotOnChain = 1
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalStateQuery/Codec.hs:codecLocalStateQuery
and cardano-diffusion/protocols/cddl/specs/local-state-query.cddl
; Acquire / Re-acquire
MsgAcquire(SpecificPoint pt) = [0, point]
MsgAcquire(VolatileTip) = [8]
MsgAcquire(ImmutableTip) = [10] ; V16+ only
MsgAcquired = [1]
MsgFailure(reason) = [2, failure_code:uint]
; 0=PointTooOld, 1=PointNotOnChain
MsgQuery(query) = [3, query_encoding]
MsgResult(result) = [4, result_encoding]
MsgRelease = [5]
MsgReAcquire(SpecificPoint pt)= [6, point]
MsgReAcquire(VolatileTip) = [9]
MsgReAcquire(ImmutableTip) = [11] ; V16+ only
MsgDone = [7]
Query Encoding (Three-Level HFC Wrapping)
Queries are wrapped in three layers. The outermost layer is the consensus-level
Query type (in Ouroboros.Consensus.Ledger.Query):
; Outermost consensus layer
query = [2, tag=0, wrapped_block_query] ; BlockQuery — delegates to HFC
/ [1, tag=1] ; GetSystemStart
/ [1, tag=2] ; GetChainBlockNo (V16+ / QueryVersion2)
/ [1, tag=3] ; GetChainPoint (V16+ / QueryVersion2)
/ [1, tag=4] ; DebugLedgerConfig (V20+ / QueryVersion3)
For BlockQuery (tag=0), the next layer is the HFC query:
; HFC (Hard Fork Combinator) layer
hfc_query = [2, tag=0, era_query] ; QueryIfCurrent — query current era
/ [3, tag=1, era_query, era_index] ; QueryAnytime
/ [2, tag=2, hf_specific] ; QueryHardFork
For QueryIfCurrent, the era index is determined by dispatch; there is no
explicit era tag in the message. The era_query is the era-level query:
; Era-level query (Shelley BlockQuery tags)
; These are 1-element or 2-element arrays with a numeric tag
era_query = [1, tag=0] ; GetLedgerTip
/ [1, tag=1] ; GetEpochNo
/ [2, tag=2, ..] ; GetNonMyopicMemberRewards
/ [1, tag=3] ; GetCurrentPParams
; ... (see full table below)
Shelley BlockQuery Tag Table
| Tag | Query Name | Min N2C Version |
|---|---|---|
| 0 | GetLedgerTip | V8 |
| 1 | GetEpochNo | V8 |
| 2 | GetNonMyopicMemberRewards | V8 |
| 3 | GetCurrentPParams | V8 |
| 4 | GetProposedPParamsUpdates | V8 (rejected from V20) |
| 5 | GetStakeDistribution | V8 (removed in V21) |
| 6 | GetUTxOByAddress | V8 |
| 7 | GetUTxOWhole | V8 |
| 8 | DebugEpochState | V8 |
| 9 | GetCBOR (wraps inner query in tag(24)) | V8 |
| 10 | GetFilteredDelegationsAndRewardAccounts | V8 |
| 11 | GetGenesisConfig | V8 |
| 12 | DebugNewEpochState | V8 |
| 13 | DebugChainDepState | V8 |
| 14 | GetRewardProvenance | V9 |
| 15 | GetUTxOByTxIn | V10 |
| 16 | GetStakePools | V11 |
| 17 | GetStakePoolParams | V11 |
| 18 | GetRewardInfoPools | V11 |
| 19 | GetPoolState | V11 |
| 20 | GetStakeSnapshots | V11 |
| 21 | GetPoolDistr | V11 (removed in V21) |
| 22 | GetStakeDelegDeposits | V16 |
| 23 | GetConstitution | V16 |
| 24 | GetGovState | V16 |
| 25 | GetDRepState | V16 |
| 26 | GetDRepStakeDistr | V16 |
| 27 | GetCommitteeMembersState | V16 |
| 28 | GetFilteredVoteDelegatees | V16 |
| 29 | GetAccountState | V16 |
| 30 | GetSPOStakeDistr | V16 |
| 31 | GetProposals | V17 |
| 32 | GetRatifyState | V17 |
| 33 | GetFuturePParams | V18 |
| 34 | GetLedgerPeerSnapshot | V19 |
| 35 | QueryStakePoolDefaultVote | V20 |
| 36 | GetPoolDistr2 | V21 |
| 37 | GetStakeDistribution2 | V21 |
| 38 | GetMaxMajorProtocolVersion | V21 |
| 39 | GetDRepDelegations | V23 |
Dugite serves every tag in this table; the dispatch lives in
crates/dugite-node/src/node/n2c_query/mod.rs. GetFuturePParams (33) always
answers Nothing. GetLedgerPeerSnapshot (34) has distinct V19–V22 and V23+
shapes. The three version-gated rejections (tag 4 from V20; tags 5 and 21 from
V21) are enforced explicitly rather than silently answered.
Beyond the Shelley BlockQuery tags, Dugite also serves the top-level queries
GetCurrentEra, GetSystemStart, GetChainBlockNo, GetChainPoint; the
QueryAnytime queries GetEraStart and GetCurrentEra; and the QueryHardFork
queries GetInterpreter (era history) and GetCurrentEra.
Source: cardano-diffusion/api/lib/Cardano/Network/NodeToClient/Version.hs and
ouroboros-consensus/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Block/Cardano.hs
MsgResult Wrapping
For QueryIfCurrent queries, the result is wrapped in an EitherMismatch
type to indicate whether the query was applied to the correct era:
; QueryIfCurrent result encoding
result = [result_value] ; Success: definite-length array(1) wrapping the value
/ [era_mismatch_info] ; Era mismatch: see EraEraMismatch encoding
A successful QueryIfCurrent result is wrapped in a 1-element definite-length
array. This is easy to miss and causes decoding failures if omitted.
QueryAnytime and QueryHardFork results are not wrapped in this extra
array.
N2C Protocol 9: LocalTxMonitor
Identity
- Protocol ID: 9
- Direction: Client monitors the node's mempool contents.
- Versions: All N2C versions.
MsgGetMeasures/MsgReplyGetMeasuresrequire N2C V20+.
State Machine
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxMonitor/Type.hs
StIdle (ClientAgency) -- client can acquire a snapshot or terminate
│
├─── MsgAcquire ──→ StAcquiring
└─── MsgDone ──→ StDone
StAcquiring (ServerAgency) -- server captures mempool snapshot
│
└─── MsgAcquired(slotNo) ──→ StAcquired
StAcquired (ClientAgency) -- client queries snapshot or releases
│
├─── MsgNextTx ──→ StBusy(NextTx)
├─── MsgHasTx(txid) ──→ StBusy(HasTx)
├─── MsgGetSizes ──→ StBusy(GetSizes)
├─── MsgGetMeasures ──→ StBusy(GetMeasures) ; V20+ only
├─── MsgAwaitAcquire ──→ StAcquiring ; refresh snapshot
└─── MsgRelease ──→ StIdle
StBusy(NextTx) (ServerAgency)
└─── MsgReplyNextTx(maybe tx) ──→ StAcquired
StBusy(HasTx) (ServerAgency)
└─── MsgReplyHasTx(bool) ──→ StAcquired
StBusy(GetSizes) (ServerAgency)
└─── MsgReplyGetSizes(sizes) ──→ StAcquired
StBusy(GetMeasures) (ServerAgency) ; V20+
└─── MsgReplyGetMeasures(m) ──→ StAcquired
StDone (NobodyAgency)
Snapshot semantics: After MsgAcquired, the client holds a fixed snapshot
of the mempool as of the slotNo returned. The snapshot does not change even
if new transactions arrive or are removed. MsgAwaitAcquire refreshes the
snapshot without going through StIdle.
Wire Format
Source: ouroboros-network/ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxMonitor/Codec.hs
and cardano-diffusion/protocols/cddl/specs/local-tx-monitor.cddl
MsgDone = [0]
MsgAcquire = [1] ; same tag for initial acquire from StIdle
MsgAwaitAcquire = [1] ; same tag for re-acquire from StAcquired
MsgAcquired = [2, slotNo:word64]
MsgRelease = [3]
MsgNextTx = [5] ; note: tag 4 is unused
MsgReplyNextTx = [6] ; no tx: empty mempool
/ [6, tx] ; with tx: next transaction in snapshot
MsgHasTx = [7, txId]
MsgReplyHasTx = [8, bool]
MsgGetSizes = [9]
MsgReplyGetSizes = [10, [capacityInBytes:word32,
sizeInBytes:word32,
numberOfTxs:word32]]
MsgGetMeasures = [11] ; V20+ only
MsgReplyGetMeasures = [12, txCount:word32, {* tstr => [integer, integer]}]
; V20+ only
Tag 4 is intentionally unused. Tags jump from 3 (MsgRelease) to 5
(MsgNextTx).
MsgReplyNextTx: Uses the same tag (6) for both the no-tx and has-tx
cases, distinguished by array length: [6] (len=1) means no more txs;
[6, tx] (len=2) means a tx follows.
MsgAcquire and MsgAwaitAcquire use the same wire tag [1]. The
protocol state (StIdle vs StAcquired) determines which message is
being decoded. This is handled by the state token in the codec.
Transaction encoding: Same as LocalTxSubmission — raw CBOR with no HFC wrapping.
txId encoding: Raw 32-byte Blake2b-256 hash as CBOR bytes primitive.
Initialization Sequence
N2N Connection Startup
After the TCP connection is established:
-
Handshake (protocol 0): Both sides send
MsgProposeVersionssimultaneously (simultaneous open). The one with the lower socket address keeps the outbound role; the other keeps the inbound. Each side processes the other's proposal and the higher-address side sendsMsgAcceptVersionorMsgRefuse. The connection proceeds only if both sides determine the same version. -
Mux starts: After successful handshake, the mux multiplexer and demultiplexer threads are started. Protocol threads are started based on peer temperature.
-
Cold→Warm:
KeepAlive(8) andPeerSharing(10) initiator threads start eagerly. -
Warm→Hot:
ChainSync(2),BlockFetch(3),TxSubmission2(4) initiator threads start eagerly. Responder threads start on-demand (when first inbound bytes arrive). -
TxSubmission2 MsgInit: The TxSubmission2 client (outbound side) must send
MsgInit([6]) as its very first message. Without this, the server stays inStInitindefinitely (waitForever timeout).
N2C Connection Startup
-
Handshake (protocol 0): Same mechanism, but using N2C version numbers (with bit 15 set). The local client proposes; the node accepts.
-
Mux starts: All N2C mini-protocols start eagerly on both sides.
-
No mandatory initial messages: Unlike N2N TxSubmission2, no N2C protocol requires a mandatory initial message before the first client request. The client may begin with
MsgAcquire(LocalStateQuery),MsgSubmitTx(LocalTxSubmission), orMsgAcquire(LocalTxMonitor) immediately.
HFC Era Index Table
This table applies to all N2N protocols (ChainSync headers, BlockFetch blocks, TxSubmission2 txids/txs) and N2C LocalChainSync blocks.
| Era Index | Era |
|---|---|
| 0 | Byron |
| 1 | Shelley (TPraos) |
| 2 | Allegra (TPraos) |
| 3 | Mary (TPraos) |
| 4 | Alonzo (TPraos) |
| 5 | Babbage (Praos) |
| 6 | Conway (Praos) |
| 7 | Dijkstra (Praos, future) |
Source: ouroboros-consensus/ouroboros-consensus-cardano/src/unstable-cardano-consensus/Ouroboros/Consensus/Cardano/Block.hs
Summary: Protocol Error Triggers
This table lists the most common protocol violations that terminate the connection.
| Protocol | Error Condition | Trigger |
|---|---|---|
| Handshake | VersionMismatch | No common version in propose |
| Handshake | Refused | Magic mismatch, policy rejection |
| Handshake | HandshakeDecodeError | Failed to decode version params |
| ChainSync | Agency violation | Client sends MsgRollForward (server-only message) |
| ChainSync | ProtocolErrorRequestNonBlocking | Server sends MsgAwaitReply but StNext(StMustReply) was active (not StCanAwait) |
| BlockFetch | Agency violation | Client sends MsgBlock (server-only message) |
| TxSubmission2 | Protocol error | Any message before MsgInit is processed |
| TxSubmission2 | BlockingReply empty | Server sends MsgRequestTxIds(blocking=true) and client replies with empty list |
| TxSubmission2 | Size mismatch | Reported SizeInBytes deviates >10 bytes from actual tx wire size (V2 inbound) |
| KeepAlive | KeepAliveCookieMissmatch | Response cookie != request cookie |
| PeerSharing | Protocol error | Server replies with more peers than requested |
| LocalStateQuery | AcquireFailurePointTooOld | SpecificPoint is outside the volatile window |
| LocalStateQuery | AcquireFailurePointNotOnChain | SpecificPoint not on the node's chain |
| LocalStateQuery | ImmutableTip on old version | Attempting MsgAcquire(ImmutableTip) before N2C V16 |
| Any | Byte limit exceeded | Ingress queue overflow (per-state byte limits) |
| Any | Timeout exceeded | Per-state timing limits (see per-protocol tables) |
Source File Index
All files are in the IntersectMBO/ouroboros-network repository (main branch)
unless otherwise noted.
| Protocol / Topic | File |
|---|---|
| N2N Handshake Type | ouroboros-network/framework/lib/Ouroboros/Network/Protocol/Handshake/Type.hs |
| N2N Handshake Codec | ouroboros-network/framework/lib/Ouroboros/Network/Protocol/Handshake/Codec.hs |
| N2N Handshake CDDL | cardano-diffusion/protocols/cddl/specs/handshake-node-to-node-v14.cddl |
| N2C Handshake CDDL | cardano-diffusion/protocols/cddl/specs/handshake-node-to-client.cddl |
| N2N Version data v14 CDDL | cardano-diffusion/protocols/cddl/specs/node-to-node-version-data-v14.cddl |
| N2N Version data v16 CDDL | cardano-diffusion/protocols/cddl/specs/node-to-node-version-data-v16.cddl |
| N2C Version enum | cardano-diffusion/api/lib/Cardano/Network/NodeToClient/Version.hs |
| N2N Version enum | cardano-diffusion/api/lib/Cardano/Network/NodeToNode/Version.hs |
| ChainSync Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/Type.hs |
| ChainSync Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/Codec.hs |
| ChainSync TimeLimits | cardano-diffusion/protocols/lib/Cardano/Network/Protocol/ChainSync/Codec/TimeLimits.hs |
| ChainSync CDDL | cardano-diffusion/protocols/cddl/specs/chain-sync.cddl |
| ChainSync Pipelining | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/ChainSync/PipelineDecision.hs |
| BlockFetch Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/BlockFetch/Type.hs |
| BlockFetch Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/BlockFetch/Codec.hs |
| BlockFetch CDDL | cardano-diffusion/protocols/cddl/specs/block-fetch.cddl |
| TxSubmission2 Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/TxSubmission2/Type.hs |
| TxSubmission2 Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/TxSubmission2/Codec.hs |
| TxSubmission2 CDDL | cardano-diffusion/protocols/cddl/specs/tx-submission2.cddl |
| KeepAlive Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/KeepAlive/Type.hs |
| KeepAlive Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/KeepAlive/Codec.hs |
| KeepAlive CDDL | cardano-diffusion/protocols/cddl/specs/keep-alive.cddl |
| PeerSharing Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/PeerSharing/Type.hs |
| PeerSharing Codec (Cardano) | cardano-diffusion/protocols/lib/Cardano/Network/Protocol/PeerSharing/Codec.hs |
| PeerSharing CDDL | cardano-diffusion/protocols/cddl/specs/peer-sharing-v14.cddl |
| LocalStateQuery Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalStateQuery/Type.hs |
| LocalStateQuery Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalStateQuery/Codec.hs |
| LocalStateQuery CDDL | cardano-diffusion/protocols/cddl/specs/local-state-query.cddl |
| LocalTxSubmission Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxSubmission/Type.hs |
| LocalTxSubmission Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxSubmission/Codec.hs |
| LocalTxSubmission CDDL | cardano-diffusion/protocols/cddl/specs/local-tx-submission.cddl |
| LocalTxMonitor Type | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxMonitor/Type.hs |
| LocalTxMonitor Codec | ouroboros-network/protocols/lib/Ouroboros/Network/Protocol/LocalTxMonitor/Codec.hs |
| LocalTxMonitor CDDL | cardano-diffusion/protocols/cddl/specs/local-tx-monitor.cddl |
| Protocol Limits (byte/time constants) | ouroboros-network/api/lib/Ouroboros/Network/Protocol/Limits.hs |
| Diffusion Configuration | cardano-diffusion/lib/Cardano/Network/Diffusion/Configuration.hs |
| Mux SDU framing | ouroboros-network/network-mux/src/Network/Mux/Types.hs |
| HFC era encoding (encodeNS) | ouroboros-consensus repo: src/.../HardFork/Combinator/Serialisation/Common.hs |
| network.base.cddl | cardano-diffusion/protocols/cddl/specs/network.base.cddl |
Upgrading Dugite
This page is the operator's upgrade path: what changes between releases, what each release requires of your database, and the exact procedure to follow.
For what changed functionally in a release, read the GitHub Releases page. This page only covers what you have to do.
The one thing that matters: SNAPSHOT_VERSION
Dugite's ledger state is persisted as a snapshot under <database-path>/ledger/,
stamped with a SNAPSHOT_VERSION byte. That number decides how much work an
upgrade costs you:
| Situation | Cost |
|---|---|
SNAPSHOT_VERSION unchanged | Drop-in. Swap the binary and restart. |
SNAPSHOT_VERSION bumped | Snapshot rejected on load, ledger rebuilt by replaying ImmutableDB chunks. Blocks are kept; expect minutes-to-hours depending on database size. |
| Import format changed | Full mithril-import required. Replay cannot repair it. |
The current value is 31, defined at
crates/dugite-ledger/src/state/snapshot.rs.
A version mismatch is never destructive. The unreadable snapshot is renamed,
not deleted — to <name>.bin.v<NN>-unreadable — and the node logs:
Quarantined unreadable ledger snapshot — chain will be replayed from ImmutableDB
on next start. Inspect or delete the .v{NN}-unreadable file once recovery completes.
Delete the .v*-unreadable files once the node is healthy again.
Upgrade matrix
Find the version you are upgrading from:
| Upgrading from | What happens | Action needed |
|---|---|---|
| Before v2.1.0 | Pre-v2.1.0 Mithril imports discarded governance roots (Proposals.pRoots), which silently corrupts reward calculation (issue #898). A chunk replay will not repair it. | Full mithril-import required — see below |
| v2.1.0 – v2.2.x | SNAPSHOT_VERSION reached 31 in v2.3.0 (from 29 in v2.1.0, 30 in v2.2.x). Snapshot is quarantined and the ledger replays from chunks. | Stop, upgrade, restart. Allow extra time on first start |
| v2.3.0 – v2.4.2 | SNAPSHOT_VERSION unchanged at 31. | Drop-in. Stop, upgrade, restart |
v2.4.3 (current release)
Drop-in. SNAPSHOT_VERSION is unchanged at 31. No re-sync, no
re-import, no snapshot wipe, no config change. Stop the node with SIGTERM,
replace the binaries, restart.
Release notes that changed operator behaviour
These are the only releases since v2.1.0 that changed anything on disk or in the shutdown/startup contract. Everything not listed here was a pure drop-in.
- v2.4.0 — added two files inside the database directory:
<db>/lockand<db>/immutable/clean. Both are created automatically and need no operator action. Consequence worth knowing:<db>/lockis an exclusive advisory flock held for the lifetime ofdugite-node run, so a second process cannot open the same database directory — it fails fast naming the holder's PID. Tools that open the ChainDB (dugite-node db info) now fail against a live node by design. See Troubleshooting. - v2.3.0 — bumped
SNAPSHOT_VERSION30 → 31 (issue #919, per-era min-UTxO). This is the last release that required a chunk replay. Existing databases replay on first restart; blocks are not discarded. - v2.1.0 — fixed the Mithril import path that dropped
Proposals.pRoots(#898). Databases imported by any earlier version must be re-imported.
Upgrade procedure
1. Stop the node — SIGTERM, never SIGKILL
# Graceful shutdown
kill $(pidof dugite-node) # SIGTERM
# or
systemctl stop dugite-node
On SIGTERM (or SIGINT / Ctrl-C) the node demotes its peers, flushes volatile
blocks to the ImmutableDB, fsyncs the chunk and index files, writes tip.meta,
stamps the immutable/clean marker, and saves a final ledger snapshot.
kill -9 skips all of that. The active chunk's index and the clean marker are
left in an unknown state, so the next start pays for an index rebuild and
possibly a chunk reconciliation. Wait for the process to actually exit — the
final snapshot has its own 120 s budget on large databases.
A second SIGTERM during shutdown forces an immediate exit (exit 143), so do
not spam the signal.
2. Install the new binaries
From a release tarball:
curl -LO https://github.com/michaeljfazio/dugite/releases/latest/download/dugite-x86_64-linux.tar.gz
tar xzf dugite-x86_64-linux.tar.gz
sudo mv dugite-node dugite-cli dugite-monitor dugite-config /usr/local/bin/
Published targets: dugite-x86_64-linux.tar.gz, dugite-aarch64-linux.tar.gz,
dugite-aarch64-macos.tar.gz. Checksums are attached to each release as
SHA256SUMS.txt.
From source:
git pull
cargo build --release
sudo cp target/release/dugite-node target/release/dugite-cli \
target/release/dugite-monitor target/release/dugite-config \
/usr/local/bin/
Container:
docker pull ghcr.io/michaeljfazio/dugite:2.4.3
Helm: the chart is published to oci://ghcr.io/michaeljfazio/charts/dugite-node
and its appVersion tracks the node release. See
Kubernetes Deployment.
3. Restart
dugite-node run \
--config config.json \
--topology topology.json \
--database-path ./db \
--socket-path ./node.sock \
--host-addr 0.0.0.0 \
--port 3001
Confirm the node resumed from the right place:
dugite-node --version
dugite-cli query tip --socket-path ./node.sock
4. Watch the first minute of logs
An upgrade is the most likely moment for a database problem to surface. These lines are the ones that matter:
| Log line | Meaning |
|---|---|
Quarantined unreadable ledger snapshot | SNAPSHOT_VERSION changed — replay is running, this is expected on a version bump |
ImmutableDB: unclean shutdown detected (no clean marker) | Previous stop was not graceful; the block index is being rebuilt |
Ledger tip is BELOW the ImmutableDB tip after replay | Ledger/immutable seam — see Troubleshooting |
database directory is locked by another dugite process | The old process has not exited yet, or a second node is pointed at the same directory |
inconsistent chunk … Refusing to open with a hole below the tip | Storage damage below the tip — recovery required, see Troubleshooting |
Re-import (only when the matrix says so)
# Stop the node FIRST — mithril-import does not take the DB lock and will
# happily delete the immutable directory out from under a running node.
kill $(pidof dugite-node)
dugite-node mithril-import --network-magic 1 --database-path ./db-preprod
Network magic: mainnet 764824073, preview 2, preprod 1. See
Mithril Snapshot Import.
Configuration compatibility
New config fields are always optional with defaults, so an existing config file keeps working across upgrades. Validate before restarting if you edited it:
dugite-config validate config.json
Most config changes can also be applied to a running node with SIGHUP
(topology and log directives reload live; restart-required fields are listed in
the log and ignored). See Troubleshooting.
Protocol version compatibility
Dugite tracks the handshake versions supported by the current cardano-node
release: N2N v14–v15 and N2C v16–v23. If the network hard-forks to an
era your build predates, peers will refuse the handshake — upgrade Dugite
before the fork. See the Mini-Protocol Reference.
Downgrading
Downgrading across a SNAPSHOT_VERSION bump does not work: the older binary
rejects the newer snapshot and there is no forward-compatible reader. It will
quarantine the snapshot and replay from the ImmutableDB, which is safe but slow.
Block storage itself is format-stable and is never the reason to wipe a
database.
Conformance Test Suite
Dugite's correctness story rests on three layers:
- Conformance — byte-exact alignment vs upstream Cardano artefacts, verified by replay. This page.
- Feature compatibility — what protocol features the node implements. See the wiki Protocol Compliance page.
- Operational soak testing — sustained behaviour on live testnets (preview, preprod) and the local devnet.
This page documents the conformance suite: where the upstream fixtures come from, what each area validates, and how to replay any of them locally.
The corpus model
upstream repos (SHA-pinned in sources.toml)
│
▼
regenerate-conformance-corpus workflow
│ produces 7 tarballs
▼
dugite GitHub release (tag pinned in manifest.toml)
│
▼
just download-upstream-fixtures
│
▼
dugite-conformance test harness (DUGITE_REQUIRE_UPSTREAM=1)
Upstream sources are pinned by commit SHA (or tag) in tests/conformance/upstream/sources.toml. The regenerate-conformance-corpus workflow consumes those pins, materialises the seven fixture areas into tarballs, and publishes them as assets of a single dugite GitHub release. Consumers (CI and local developers alike) then pin to that release tag via tests/conformance/upstream/manifest.toml, so a single fetch lands every fixture area at a known good combination. Tarballs are cached by content hash of manifest.toml, so bumping the tag invalidates the cache automatically.
This two-level pinning separates "what upstream version we test against" (sources.toml, only changes when we want to bump) from "what corpus the test run consumed" (manifest.toml, deterministic and cacheable).
Fixtures land in tests/conformance/upstream/fixtures/, which is gitignored — nothing in the corpus is committed. To fetch a single area rather than all seven, use just download-upstream-fixtures-area <AREA> (equivalently cargo xtask download-upstream-fixtures --area <AREA>), where <AREA> is one of the seven area names listed below.
Status
| Area | Source | Coverage | Status |
|---|---|---|---|
| UPLC (Plutus) | IntersectMBO/plutus | 1003 evaluation cases | 100% — skip list empty |
| ouroboros-consensus | IntersectMBO/ouroboros-consensus | Block / header golden files per era | passing |
| cardano-ledger | IntersectMBO/cardano-ledger | Genesis JSON, CDDL schema, golden transactions | passing |
| cardano-node | IntersectMBO/cardano-node | Genesis spec files | passing |
| ledger-rules (ImpSpec) | IntersectMBO/cardano-ledger | ~8100 CBOR STS-rule vectors from ImpSpec, across 11 rule families | passing — SKIP_LIST empty |
| cardano-base | IntersectMBO/cardano-base | VRF v03 crypto test vectors | passing |
| mithril | input-output-hk/mithril | Certificate fixture JSON | passing |
Per-area detail
just test-conformance is the whole suite: test-conformance-uplc (crate
dugite-uplc, --test conformance) plus test-conformance-upstream (crate
dugite-conformance, --test upstream_tests). The six single-area recipes
below are nextest filters over the latter. Because they are filters, nextest
reports the other areas as skipped — that is the filter working, not a
coverage gap.
UPLC (Plutus)
Source: IntersectMBO/plutus, pinned to tag 1.66.0.0 in sources.toml.
What's validated: 1003 evaluation test cases from plutus-conformance/test-cases/uplc/evaluation/. Each test case provides a UPLC program and the expected result (a term, a budget exhaustion, or a specific runtime error). The dugite-uplc CEK machine evaluates each program and the harness compares term-for-term, budget-for-budget against the expected output.
Status: 100% passing. The skip list (crates/dugite-uplc/tests/conformance_skip.txt) has been empty since v1.7.0 — it currently contains only comments. The build script fails loudly if a skip entry names a directory that is not in the downloaded corpus, so a stale entry cannot silently hide a fix. The harness covers normalisation by evaluation (NbE) readback, per-builtin cost model wiring, CIP-122 bit ordering, BLS LE scalar handling with null augmentation, and BIP-340 verify_raw semantics (not the SHA-256-wrapped verify).
This pin is version-coupled, not just a version bump. The UPLC parser (
syn::parser::parse_value_literal) tracks the corpus's own semantics — 1.66.0.0 reworkedbuiltin/constant/value(key-*→currencyID-*/tokenID-*) and changed non-canonicalvalueliterals from normalised to rejected.sources.tomlandmanifest.tomlmust therefore advance together; bumping one alone breaks the suite.
Replay locally:
just download-upstream-fixtures
just test-conformance-uplc
ouroboros-consensus
Source: IntersectMBO/ouroboros-consensus, SHA-pinned in sources.toml.
What's validated: Era-tagged golden files for Cardano blocks and headers. The harness exercises the in-house multi-era CBOR decoder against fixtures captured directly from the upstream Haskell encoders, asserting round-trip and structural equivalence per era.
Status: passing across all eras (Byron, Shelley, Allegra, Mary, Alonzo, Babbage, Conway).
Replay locally:
just download-upstream-fixtures
just test-conformance-ouroboros-consensus
cardano-ledger
Source: IntersectMBO/cardano-ledger, SHA-pinned in sources.toml.
What's validated: Three classes of fixture. Genesis JSON for each era is parsed and structurally compared. The CDDL schema is loaded and exercised against representative documents. Golden transaction CBOR is decoded and asserted for byte-equality on re-encode.
Status: passing.
Replay locally:
just download-upstream-fixtures
just test-conformance-cardano-ledger
cardano-node
Source: IntersectMBO/cardano-node, SHA-pinned in sources.toml.
What's validated: Genesis spec files (shelley-genesis.json, alonzo-genesis.json, conway-genesis.json and their Byron counterpart). The harness asserts that dugite parses each spec into its internal genesis types and that the resulting types preserve every documented field.
Status: passing.
Replay locally:
just download-upstream-fixtures
just test-conformance-cardano-node
ledger-rules (ImpSpec)
Source: IntersectMBO/cardano-ledger ImpSpec, SHA-pinned in sources.toml. The corpus regeneration pipeline builds cardano-ledger from source (GHC 9.6.5 + cabal 3.10.x, ≈35 min cold / 5 min cached) and runs the upstream ImpSpec conformance suite with CONFORMANCE_CBOR_DUMP_PATH set to capture every test vector as CBOR.
What's validated: Eleven STS-rule families, not just the two headline ones — NEWEPOCH and ConwayNEWEPOCH (epoch-boundary transitions), LEDGER (transaction application), POOL, CERT, CERTS, DELEG, GOVCERT, GOV, ENACT, and RATIFY. The current corpus holds roughly 8,100 captured test-case directories. The harness replays each CBOR vector through the corresponding dugite ledger code path and compares the resulting state byte-for-byte.
Note that an empty SKIP_LIST means no vector is skipped by policy. A vector can still report Skipped at runtime if the runner cannot construct its precondition; that is visible in the run output, not hidden by the list.
Status: passing. SKIP_LIST in tests/conformance/src/upstream/ledger_rules_replay/mod.rs is empty.
Replay locally:
just download-upstream-fixtures
just test-conformance-ledger-rules
cardano-base
Source: IntersectMBO/cardano-base, SHA-pinned in sources.toml.
What's validated: VRF v03 test vectors. Each vector ships an input message, a signing key, an expected proof, and an expected output hash. The harness exercises the dugite VRF implementation against every vector and asserts byte-equality on both the proof and the output, which is what guarantees Praos-compatible leader election.
Status: passing.
Replay locally:
just download-upstream-fixtures
just test-conformance-cardano-base
mithril
Source: input-output-hk/mithril, SHA-pinned in sources.toml.
What's validated: Mithril certificate fixture JSON. The harness loads each certificate, verifies the aggregate signature, and asserts structural equivalence against the upstream-captured form.
Status: passing.
Replay locally:
just download-upstream-fixtures
just test-conformance-mithril
CI integration
The upstream-conformance job in .github/workflows/ci.yml runs both the UPLC suite and the upstream tests with the DUGITE_REQUIRE_UPSTREAM=1 environment variable set. This variable makes a missing fixture a hard failure rather than a silent skip — the gate exists specifically to stop the suite from quietly degrading to a no-op when something is wrong with the fixture cache or download.
Fixture tarballs are cached on the CI runner, keyed by the SHA-256 content hash of tests/conformance/upstream/manifest.toml. Bumping [release].tag in that file invalidates the cache automatically; no separate cache-bust step is needed.
Updating the corpus
To adopt a new upstream version:
- Edit
tests/conformance/upstream/sources.toml, bumping the SHA (or tag for theplutusarea) of the area you want to refresh. - Run the
regenerate-conformance-corpusworkflow. It fires on three triggers: weekly cron (Sundays 02:00 UTC), manual dispatch (with optional per-area SHA/tag overrides patched intosources.tomlfor that run), and any push tomaintouchingsources.tomlorscripts/regenerate-conformance-corpus/**. It produces a new dugite release taggedconformance-corpus-v<YYYYmmdd-HHMMSS>with the seven tarballs plus acorpus-manifest.json. - The workflow then opens an adoption PR itself (
chore/adopt-<NEW_TAG>, titledchore(conformance): adopt corpus <NEW_TAG>) rewriting[release].taginmanifest.toml. This exists because the workflow used to publish releases nothing pointed at, and the pinned corpus silently drifted about two months stale. If you are adopting by hand, edit[release].tagyourself. - Run
just download-upstream-fixtures && just test-conformancelocally. - Fix any test fallout, then commit the
sources.toml+manifest.tomlupdates together with the code changes.
To iterate on a capture script without publishing a release, just regenerate-corpus-local runs the same pipeline into target/conformance-corpus/<tag>/.
Currently pinned
[release].tag = "conformance-corpus-v20260725-154355", built from:
| Area | Pin |
|---|---|
| ouroboros-consensus | f205a7103deb732cc07cabd51fa76ce22f84f0d0 |
| cardano-ledger | a88b60bdcf3248dfe5a2f9372c188c399233f479 |
| cardano-node | 0a21a7437fb9d38060b297c5997275d316e60d5c |
| plutus | tag 1.66.0.0 |
| ledger-rules | a88b60bdcf3248dfe5a2f9372c188c399233f479 (same tree as cardano-ledger) |
| cardano-base | 12168e4b32b44d30dd401010ccd969accaf2add7 |
| mithril | 2eedbd254e6bb656f6c10ec83930327dd0768a4a |
See also
- Benchmarks — performance evidence.
- Wiki Protocol Compliance — feature-by-feature compatibility catalogue.
- Wiki Known Issues — open gaps and follow-ups.
Nightly Benchmark Results — 2026-08-12
Captured on 2026-08-12 from commit 59368e9 on main (GitHub Actions ubuntu-latest).
Measurement environment
| Runner | GitHub Actions ubuntu-latest (shared, virtualised) |
| CPU | AMD EPYC 7763 64-Core Processor |
| vCPUs | 4 |
| Memory | 15.6 GiB |
| Kernel | Linux 6.17.0-1020-azure |
| Toolchain | rustc 1.97.0 (2d8144b78 2026-07-07) |
| Build profile | bench (release + debug assertions off) |
Read absolute numbers with care. These run on shared, virtualised GitHub-hosted runners whose CPU model is not pinned and whose neighbours are not controlled. Treat the figures as an order-of-magnitude regression tripwire, not as hardware benchmarks: a
change: ±x%between two dates can reflect a different host class rather than a code change. Use the interactive trend lines below, where a real regression shows as a sustained step rather than a single-day spike.
Not measured here: end-to-end sync throughput, mainnet-scale UTxO memory, and anything requiring a live network. Those are covered by the devnet-validate and soak rigs, not by Criterion.
Interactive reports, including per-benchmark detail pages and historical trend lines, are published at https://michaeljfazio.github.io/dugite/benchmarks/. Each section below also links directly to its interactive report.
The collapsed Raw measurements blocks contain the filtered
cargo benchoutput (cargo build chatter and ANSI escapes are stripped). Full unfiltered logs are uploaded as thebenchmark-results-160workflow artifact.
Storage
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking chaindb/sequential_insert/10k_20kb
Benchmarking chaindb/sequential_insert/10k_20kb: Warming up for 3.0000 s
Benchmarking chaindb/sequential_insert/10k_20kb: Collecting 10 samples in estimated 5.8547 s (30 iterations)
Benchmarking chaindb/sequential_insert/10k_20kb: Analyzing
chaindb/sequential_insert/10k_20kb
time: [185.61 ms 189.10 ms 192.73 ms]
Benchmarking chaindb/random_read/by_hash/10000blks
Benchmarking chaindb/random_read/by_hash/10000blks: Warming up for 3.0000 s
Benchmarking chaindb/random_read/by_hash/10000blks: Collecting 100 samples in estimated 7.3887 s (15k iterations)
Benchmarking chaindb/random_read/by_hash/10000blks: Analyzing
chaindb/random_read/by_hash/10000blks
time: [486.31 µs 491.41 µs 496.40 µs]
Benchmarking chaindb/random_read/by_hash/100000blks
Benchmarking chaindb/random_read/by_hash/100000blks: Warming up for 3.0000 s
Benchmarking chaindb/random_read/by_hash/100000blks: Collecting 100 samples in estimated 5.5445 s (10k iterations)
Benchmarking chaindb/random_read/by_hash/100000blks: Analyzing
chaindb/random_read/by_hash/100000blks
time: [535.95 µs 539.47 µs 543.00 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking chaindb/tip_query
Benchmarking chaindb/tip_query: Warming up for 3.0000 s
Benchmarking chaindb/tip_query: Collecting 100 samples in estimated 5.0000 s (14B iterations)
Benchmarking chaindb/tip_query: Analyzing
chaindb/tip_query time: [361.18 ps 364.92 ps 368.57 ps]
Benchmarking chaindb/has_block
Benchmarking chaindb/has_block: Warming up for 3.0000 s
Benchmarking chaindb/has_block: Collecting 100 samples in estimated 5.0505 s (222k iterations)
Benchmarking chaindb/has_block: Analyzing
chaindb/has_block time: [23.304 µs 23.562 µs 23.824 µs]
Benchmarking chaindb/slot_range_100
Benchmarking chaindb/slot_range_100: Warming up for 3.0000 s
Benchmarking chaindb/slot_range_100: Collecting 100 samples in estimated 5.0004 s (18M iterations)
Benchmarking chaindb/slot_range_100: Analyzing
chaindb/slot_range_100 time: [275.73 ns 279.22 ns 282.81 ns]
Benchmarking chaindb/flush_to_immutable/k_2160_blocks_20kb/2160
Benchmarking chaindb/flush_to_immutable/k_2160_blocks_20kb/2160: Warming up for 3.0000 s
Benchmarking chaindb/flush_to_immutable/k_2160_blocks_20kb/2160: Collecting 10 samples in estimated 7.0469 s (165 iterations)
Benchmarking chaindb/flush_to_immutable/k_2160_blocks_20kb/2160: Analyzing
chaindb/flush_to_immutable/k_2160_blocks_20kb/2160
time: [6.6635 ms 6.8233 ms 7.1061 ms]
Benchmarking chaindb/profile_comparison/insert_10k_20kb/in_memory
Benchmarking chaindb/profile_comparison/insert_10k_20kb/in_memory: Warming up for 3.0000 s
Benchmarking chaindb/profile_comparison/insert_10k_20kb/in_memory: Collecting 10 samples in estimated 5.5894 s (30 iterations)
Benchmarking chaindb/profile_comparison/insert_10k_20kb/in_memory: Analyzing
chaindb/profile_comparison/insert_10k_20kb/in_memory
time: [189.48 ms 191.35 ms 193.17 ms]
Benchmarking chaindb/profile_comparison/insert_10k_20kb/mmap
Benchmarking chaindb/profile_comparison/insert_10k_20kb/mmap: Warming up for 3.0000 s
Benchmarking chaindb/profile_comparison/insert_10k_20kb/mmap: Collecting 10 samples in estimated 5.5459 s (30 iterations)
Benchmarking chaindb/profile_comparison/insert_10k_20kb/mmap: Analyzing
chaindb/profile_comparison/insert_10k_20kb/mmap
time: [183.98 ms 187.61 ms 190.93 ms]
Benchmarking chaindb/profile_comparison/read_500/in_memory
Benchmarking chaindb/profile_comparison/read_500/in_memory: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 10.0s or enable flat sampling.
Benchmarking chaindb/profile_comparison/read_500/in_memory: Collecting 10 samples in estimated 9.9513 s (55 iterations)
Benchmarking chaindb/profile_comparison/read_500/in_memory: Analyzing
chaindb/profile_comparison/read_500/in_memory
time: [29.385 ms 29.864 ms 30.224 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
Benchmarking chaindb/profile_comparison/read_500/mmap
Benchmarking chaindb/profile_comparison/read_500/mmap: Warming up for 3.0000 s
Benchmarking chaindb/profile_comparison/read_500/mmap: Collecting 10 samples in estimated 5.5378 s (30 iterations)
Benchmarking chaindb/profile_comparison/read_500/mmap: Analyzing
chaindb/profile_comparison/read_500/mmap
time: [27.734 ms 28.249 ms 28.774 ms]
Benchmarking immutabledb/open/in_memory/10000
Benchmarking immutabledb/open/in_memory/10000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.0s, or reduce sample count to 60.
Benchmarking immutabledb/open/in_memory/10000: Collecting 100 samples in estimated 8.0250 s (100 iterations)
Benchmarking immutabledb/open/in_memory/10000: Analyzing
immutabledb/open/in_memory/10000
time: [80.197 ms 80.838 ms 81.513 ms]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
Benchmarking immutabledb/open/mmap_cached/10000
Benchmarking immutabledb/open/mmap_cached/10000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 7.9s, or reduce sample count to 60.
Benchmarking immutabledb/open/mmap_cached/10000: Collecting 100 samples in estimated 7.9261 s (100 iterations)
Benchmarking immutabledb/open/mmap_cached/10000: Analyzing
immutabledb/open/mmap_cached/10000
time: [79.450 ms 80.268 ms 81.250 ms]
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
Benchmarking immutabledb/open/mmap_cold_rebuild/10000
Benchmarking immutabledb/open/mmap_cold_rebuild/10000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.9s, or reduce sample count to 50.
Benchmarking immutabledb/open/mmap_cold_rebuild/10000: Collecting 100 samples in estimated 8.8794 s (100 iterations)
Benchmarking immutabledb/open/mmap_cold_rebuild/10000: Analyzing
immutabledb/open/mmap_cold_rebuild/10000
time: [79.615 ms 80.178 ms 80.743 ms]
Benchmarking immutabledb/open/in_memory/100000
Benchmarking immutabledb/open/in_memory/100000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 100.1s, or reduce sample count to 10.
Benchmarking immutabledb/open/in_memory/100000: Collecting 100 samples in estimated 100.05 s (100 iterations)
Benchmarking immutabledb/open/in_memory/100000: Analyzing
immutabledb/open/in_memory/100000
time: [776.46 ms 816.21 ms 861.29 ms]
Found 18 outliers among 100 measurements (18.00%)
2 (2.00%) high mild
16 (16.00%) high severe
Benchmarking immutabledb/open/mmap_cached/100000
Benchmarking immutabledb/open/mmap_cached/100000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 72.5s, or reduce sample count to 10.
Benchmarking immutabledb/open/mmap_cached/100000: Collecting 100 samples in estimated 72.534 s (100 iterations)
Benchmarking immutabledb/open/mmap_cached/100000: Analyzing
immutabledb/open/mmap_cached/100000
time: [717.94 ms 722.56 ms 727.31 ms]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking immutabledb/open/mmap_cold_rebuild/100000
Benchmarking immutabledb/open/mmap_cold_rebuild/100000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 72.2s, or reduce sample count to 10.
Benchmarking immutabledb/open/mmap_cold_rebuild/100000: Collecting 100 samples in estimated 72.155 s (100 iterations)
Benchmarking immutabledb/open/mmap_cold_rebuild/100000: Analyzing
immutabledb/open/mmap_cold_rebuild/100000
time: [727.15 ms 732.37 ms 737.53 ms]
Benchmarking immutabledb/lookup/in_memory/10000
Benchmarking immutabledb/lookup/in_memory/10000: Warming up for 3.0000 s
Benchmarking immutabledb/lookup/in_memory/10000: Collecting 100 samples in estimated 5.2882 s (600 iterations)
Benchmarking immutabledb/lookup/in_memory/10000: Analyzing
immutabledb/lookup/in_memory/10000
time: [8.7078 ms 8.7481 ms 8.7917 ms]
Found 5 outliers among 100 measurements (5.00%)
3 (3.00%) high mild
2 (2.00%) high severe
Benchmarking immutabledb/lookup/mmap/10000
Benchmarking immutabledb/lookup/mmap/10000: Warming up for 3.0000 s
Benchmarking immutabledb/lookup/mmap/10000: Collecting 100 samples in estimated 5.2259 s (600 iterations)
Benchmarking immutabledb/lookup/mmap/10000: Analyzing
immutabledb/lookup/mmap/10000
time: [8.7986 ms 8.8384 ms 8.8799 ms]
Found 8 outliers among 100 measurements (8.00%)
8 (8.00%) high mild
Benchmarking immutabledb/has_block/in_memory
Benchmarking immutabledb/has_block/in_memory: Warming up for 3.0000 s
Benchmarking immutabledb/has_block/in_memory: Collecting 100 samples in estimated 5.0682 s (172k iterations)
Benchmarking immutabledb/has_block/in_memory: Analyzing
immutabledb/has_block/in_memory
time: [30.270 µs 30.548 µs 30.819 µs]
Found 28 outliers among 100 measurements (28.00%)
17 (17.00%) low severe
1 (1.00%) high mild
10 (10.00%) high severe
Benchmarking immutabledb/has_block/mmap
Benchmarking immutabledb/has_block/mmap: Warming up for 3.0000 s
Benchmarking immutabledb/has_block/mmap: Collecting 100 samples in estimated 5.0543 s (172k iterations)
Benchmarking immutabledb/has_block/mmap: Analyzing
immutabledb/has_block/mmap
time: [30.117 µs 30.197 µs 30.279 µs]
Found 18 outliers among 100 measurements (18.00%)
12 (12.00%) low severe
6 (6.00%) high severe
Benchmarking immutabledb/append/1k_blocks_20kb/in_memory
Benchmarking immutabledb/append/1k_blocks_20kb/in_memory: Warming up for 3.0000 s
Benchmarking immutabledb/append/1k_blocks_20kb/in_memory: Collecting 100 samples in estimated 5.7256 s (400 iterations)
Benchmarking immutabledb/append/1k_blocks_20kb/in_memory: Analyzing
immutabledb/append/1k_blocks_20kb/in_memory
time: [14.202 ms 14.336 ms 14.473 ms]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking immutabledb/append/1k_blocks_20kb/mmap
Benchmarking immutabledb/append/1k_blocks_20kb/mmap: Warming up for 3.0000 s
Benchmarking immutabledb/append/1k_blocks_20kb/mmap: Collecting 100 samples in estimated 6.0521 s (400 iterations)
Benchmarking immutabledb/append/1k_blocks_20kb/mmap: Analyzing
immutabledb/append/1k_blocks_20kb/mmap
time: [14.992 ms 15.108 ms 15.262 ms]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
Benchmarking immutabledb/slot_range/range_100/in_memory
Benchmarking immutabledb/slot_range/range_100/in_memory: Warming up for 3.0000 s
Benchmarking immutabledb/slot_range/range_100/in_memory: Collecting 100 samples in estimated 5.0948 s (10k iterations)
Benchmarking immutabledb/slot_range/range_100/in_memory: Analyzing
immutabledb/slot_range/range_100/in_memory
time: [496.95 µs 499.43 µs 502.06 µs]
Found 6 outliers among 100 measurements (6.00%)
2 (2.00%) low mild
4 (4.00%) high mild
Benchmarking immutabledb/slot_range/range_100/mmap
Benchmarking immutabledb/slot_range/range_100/mmap: Warming up for 3.0000 s
Benchmarking immutabledb/slot_range/range_100/mmap: Collecting 100 samples in estimated 7.2013 s (15k iterations)
Benchmarking immutabledb/slot_range/range_100/mmap: Analyzing
immutabledb/slot_range/range_100/mmap
time: [475.29 µs 478.26 µs 481.17 µs]
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) low mild
3 (3.00%) high mild
Benchmarking block_index/insert/in_memory/10000
Benchmarking block_index/insert/in_memory/10000: Warming up for 3.0000 s
Benchmarking block_index/insert/in_memory/10000: Collecting 100 samples in estimated 6.5742 s (10k iterations)
Benchmarking block_index/insert/in_memory/10000: Analyzing
block_index/insert/in_memory/10000
time: [654.59 µs 656.83 µs 660.11 µs]
Found 12 outliers among 100 measurements (12.00%)
1 (1.00%) low severe
3 (3.00%) high mild
8 (8.00%) high severe
Benchmarking block_index/insert/mmap/10000
Benchmarking block_index/insert/mmap/10000: Warming up for 3.0000 s
Benchmarking block_index/insert/mmap/10000: Collecting 100 samples in estimated 6.2377 s (300 iterations)
Benchmarking block_index/insert/mmap/10000: Analyzing
block_index/insert/mmap/10000
time: [8.1911 ms 13.156 ms 21.243 ms]
Found 17 outliers among 100 measurements (17.00%)
5 (5.00%) high mild
12 (12.00%) high severe
Benchmarking block_index/insert/in_memory/50000
Benchmarking block_index/insert/in_memory/50000: Warming up for 3.0000 s
Benchmarking block_index/insert/in_memory/50000: Collecting 100 samples in estimated 5.2225 s (1400 iterations)
Benchmarking block_index/insert/in_memory/50000: Analyzing
block_index/insert/in_memory/50000
time: [3.5983 ms 3.6587 ms 3.7203 ms]
Benchmarking block_index/insert/mmap/50000
Benchmarking block_index/insert/mmap/50000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 9.0s, or reduce sample count to 50.
Benchmarking block_index/insert/mmap/50000: Collecting 100 samples in estimated 9.0239 s (100 iterations)
Benchmarking block_index/insert/mmap/50000: Analyzing
block_index/insert/mmap/50000
time: [92.774 ms 93.586 ms 94.529 ms]
Found 8 outliers among 100 measurements (8.00%)
1 (1.00%) low severe
2 (2.00%) low mild
1 (1.00%) high mild
4 (4.00%) high severe
Benchmarking block_index/insert/in_memory/100000
Benchmarking block_index/insert/in_memory/100000: Warming up for 3.0000 s
Benchmarking block_index/insert/in_memory/100000: Collecting 100 samples in estimated 5.9426 s (600 iterations)
Benchmarking block_index/insert/in_memory/100000: Analyzing
block_index/insert/in_memory/100000
time: [9.9396 ms 10.033 ms 10.126 ms]
Benchmarking block_index/insert/mmap/100000
Benchmarking block_index/insert/mmap/100000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 18.7s, or reduce sample count to 20.
Benchmarking block_index/insert/mmap/100000: Collecting 100 samples in estimated 18.701 s (100 iterations)
Benchmarking block_index/insert/mmap/100000: Analyzing
block_index/insert/mmap/100000
time: [186.85 ms 188.01 ms 189.06 ms]
Found 6 outliers among 100 measurements (6.00%)
3 (3.00%) low severe
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking block_index/lookup/in_memory/10000
Benchmarking block_index/lookup/in_memory/10000: Warming up for 3.0000 s
Benchmarking block_index/lookup/in_memory/10000: Collecting 100 samples in estimated 5.0652 s (384k iterations)
Benchmarking block_index/lookup/in_memory/10000: Analyzing
block_index/lookup/in_memory/10000
time: [13.124 µs 13.162 µs 13.225 µs]
Found 7 outliers among 100 measurements (7.00%)
1 (1.00%) low mild
4 (4.00%) high mild
2 (2.00%) high severe
Benchmarking block_index/lookup/mmap/10000
Benchmarking block_index/lookup/mmap/10000: Warming up for 3.0000 s
Benchmarking block_index/lookup/mmap/10000: Collecting 100 samples in estimated 5.0169 s (202k iterations)
Benchmarking block_index/lookup/mmap/10000: Analyzing
block_index/lookup/mmap/10000
time: [25.186 µs 25.234 µs 25.279 µs]
Found 14 outliers among 100 measurements (14.00%)
9 (9.00%) low mild
4 (4.00%) high mild
1 (1.00%) high severe
Benchmarking block_index/lookup/in_memory/50000
Benchmarking block_index/lookup/in_memory/50000: Warming up for 3.0000 s
Benchmarking block_index/lookup/in_memory/50000: Collecting 100 samples in estimated 5.0251 s (369k iterations)
Benchmarking block_index/lookup/in_memory/50000: Analyzing
block_index/lookup/in_memory/50000
time: [13.633 µs 13.677 µs 13.745 µs]
Found 9 outliers among 100 measurements (9.00%)
1 (1.00%) low mild
1 (1.00%) high mild
7 (7.00%) high severe
Benchmarking block_index/lookup/mmap/50000
Benchmarking block_index/lookup/mmap/50000: Warming up for 3.0000 s
Benchmarking block_index/lookup/mmap/50000: Collecting 100 samples in estimated 5.0066 s (268k iterations)
Benchmarking block_index/lookup/mmap/50000: Analyzing
block_index/lookup/mmap/50000
time: [18.857 µs 18.888 µs 18.918 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking block_index/lookup/in_memory/100000
Benchmarking block_index/lookup/in_memory/100000: Warming up for 3.0000 s
Benchmarking block_index/lookup/in_memory/100000: Collecting 100 samples in estimated 5.0060 s (364k iterations)
Benchmarking block_index/lookup/in_memory/100000: Analyzing
block_index/lookup/in_memory/100000
time: [13.737 µs 13.763 µs 13.792 µs]
Found 9 outliers among 100 measurements (9.00%)
8 (8.00%) high mild
1 (1.00%) high severe
Benchmarking block_index/lookup/mmap/100000
Benchmarking block_index/lookup/mmap/100000: Warming up for 3.0000 s
Benchmarking block_index/lookup/mmap/100000: Collecting 100 samples in estimated 5.0620 s (278k iterations)
Benchmarking block_index/lookup/mmap/100000: Analyzing
block_index/lookup/mmap/100000
time: [18.361 µs 18.416 µs 18.478 µs]
Found 4 outliers among 100 measurements (4.00%)
2 (2.00%) high mild
2 (2.00%) high severe
Benchmarking block_index/contains_miss/in_memory
Benchmarking block_index/contains_miss/in_memory: Warming up for 3.0000 s
Benchmarking block_index/contains_miss/in_memory: Collecting 100 samples in estimated 5.0193 s (460k iterations)
Benchmarking block_index/contains_miss/in_memory: Analyzing
block_index/contains_miss/in_memory
time: [10.903 µs 10.915 µs 10.927 µs]
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe
Benchmarking block_index/contains_miss/mmap
Benchmarking block_index/contains_miss/mmap: Warming up for 3.0000 s
Benchmarking block_index/contains_miss/mmap: Collecting 100 samples in estimated 5.1107 s (136k iterations)
Benchmarking block_index/contains_miss/mmap: Analyzing
block_index/contains_miss/mmap
time: [36.622 µs 36.876 µs 37.148 µs]
Benchmarking scaling/block_index_insert/in_memory/10000
Benchmarking scaling/block_index_insert/in_memory/10000: Warming up for 3.0000 s
Benchmarking scaling/block_index_insert/in_memory/10000: Collecting 10 samples in estimated 5.0235 s (7810 iterations)
Benchmarking scaling/block_index_insert/in_memory/10000: Analyzing
scaling/block_index_insert/in_memory/10000
time: [640.66 µs 641.71 µs 643.40 µs]
Benchmarking scaling/block_index_insert/mmap/10000
Benchmarking scaling/block_index_insert/mmap/10000: Warming up for 3.0000 s
Benchmarking scaling/block_index_insert/mmap/10000: Collecting 10 samples in estimated 5.1587 s (495 iterations)
Benchmarking scaling/block_index_insert/mmap/10000: Analyzing
scaling/block_index_insert/mmap/10000
time: [7.5768 ms 8.4552 ms 10.081 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
Benchmarking scaling/block_index_insert/in_memory/50000
Benchmarking scaling/block_index_insert/in_memory/50000: Warming up for 3.0000 s
Benchmarking scaling/block_index_insert/in_memory/50000: Collecting 10 samples in estimated 5.0716 s (1705 iterations)
Benchmarking scaling/block_index_insert/in_memory/50000: Analyzing
scaling/block_index_insert/in_memory/50000
time: [2.9715 ms 2.9930 ms 3.0106 ms]
Benchmarking scaling/block_index_insert/mmap/50000
Benchmarking scaling/block_index_insert/mmap/50000: Warming up for 3.0000 s
Benchmarking scaling/block_index_insert/mmap/50000: Collecting 10 samples in estimated 8.2071 s (110 iterations)
Benchmarking scaling/block_index_insert/mmap/50000: Analyzing
scaling/block_index_insert/mmap/50000
time: [71.285 ms 71.527 ms 71.902 ms]
Benchmarking scaling/block_index_insert/in_memory/100000
Benchmarking scaling/block_index_insert/in_memory/100000: Warming up for 3.0000 s
Benchmarking scaling/block_index_insert/in_memory/100000: Collecting 10 samples in estimated 5.2687 s (715 iterations)
Benchmarking scaling/block_index_insert/in_memory/100000: Analyzing
scaling/block_index_insert/in_memory/100000
time: [7.1815 ms 7.3013 ms 7.3886 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) low severe
Benchmarking scaling/block_index_insert/mmap/100000
Benchmarking scaling/block_index_insert/mmap/100000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 7.8s or enable flat sampling.
Benchmarking scaling/block_index_insert/mmap/100000: Collecting 10 samples in estimated 7.8492 s (55 iterations)
Benchmarking scaling/block_index_insert/mmap/100000: Analyzing
scaling/block_index_insert/mmap/100000
time: [143.14 ms 144.35 ms 145.30 ms]
Benchmarking scaling/block_index_lookup/in_memory/10000
Benchmarking scaling/block_index_lookup/in_memory/10000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/in_memory/10000: Collecting 10 samples in estimated 5.0003 s (387k iterations)
Benchmarking scaling/block_index_lookup/in_memory/10000: Analyzing
scaling/block_index_lookup/in_memory/10000
time: [12.939 µs 13.003 µs 13.102 µs]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe
Benchmarking scaling/block_index_lookup/mmap/10000
Benchmarking scaling/block_index_lookup/mmap/10000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/mmap/10000: Collecting 10 samples in estimated 5.0002 s (203k iterations)
Benchmarking scaling/block_index_lookup/mmap/10000: Analyzing
scaling/block_index_lookup/mmap/10000
time: [24.771 µs 24.835 µs 24.940 µs]
Found 2 outliers among 10 measurements (20.00%)
1 (10.00%) low mild
1 (10.00%) high mild
Benchmarking scaling/block_index_lookup/in_memory/50000
Benchmarking scaling/block_index_lookup/in_memory/50000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/in_memory/50000: Collecting 10 samples in estimated 5.0004 s (377k iterations)
Benchmarking scaling/block_index_lookup/in_memory/50000: Analyzing
scaling/block_index_lookup/in_memory/50000
time: [13.284 µs 13.295 µs 13.305 µs]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) low mild
Benchmarking scaling/block_index_lookup/mmap/50000
Benchmarking scaling/block_index_lookup/mmap/50000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/mmap/50000: Collecting 10 samples in estimated 5.0008 s (271k iterations)
Benchmarking scaling/block_index_lookup/mmap/50000: Analyzing
scaling/block_index_lookup/mmap/50000
time: [18.609 µs 18.644 µs 18.699 µs]
Found 2 outliers among 10 measurements (20.00%)
1 (10.00%) low severe
1 (10.00%) high severe
Benchmarking scaling/block_index_lookup/in_memory/100000
Benchmarking scaling/block_index_lookup/in_memory/100000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/in_memory/100000: Collecting 10 samples in estimated 5.0007 s (374k iterations)
Benchmarking scaling/block_index_lookup/in_memory/100000: Analyzing
scaling/block_index_lookup/in_memory/100000
time: [13.392 µs 13.489 µs 13.587 µs]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe
Benchmarking scaling/block_index_lookup/mmap/100000
Benchmarking scaling/block_index_lookup/mmap/100000: Warming up for 3.0000 s
Benchmarking scaling/block_index_lookup/mmap/100000: Collecting 10 samples in estimated 5.0004 s (278k iterations)
Benchmarking scaling/block_index_lookup/mmap/100000: Analyzing
scaling/block_index_lookup/mmap/100000
time: [18.051 µs 18.075 µs 18.123 µs]
Benchmarking scaling/immutabledb_open/in_memory/10000
Benchmarking scaling/immutabledb_open/in_memory/10000: Warming up for 3.0000 s
Benchmarking scaling/immutabledb_open/in_memory/10000: Collecting 10 samples in estimated 6.6483 s (110 iterations)
Benchmarking scaling/immutabledb_open/in_memory/10000: Analyzing
scaling/immutabledb_open/in_memory/10000
time: [75.516 ms 90.901 ms 107.71 ms]
Benchmarking scaling/immutabledb_open/mmap_cached/10000
Benchmarking scaling/immutabledb_open/mmap_cached/10000: Warming up for 3.0000 s
Benchmarking scaling/immutabledb_open/mmap_cached/10000: Collecting 10 samples in estimated 6.5215 s (110 iterations)
Benchmarking scaling/immutabledb_open/mmap_cached/10000: Analyzing
scaling/immutabledb_open/mmap_cached/10000
time: [57.641 ms 58.175 ms 58.708 ms]
Found 3 outliers among 10 measurements (30.00%)
1 (10.00%) low severe
1 (10.00%) low mild
1 (10.00%) high mild
Benchmarking scaling/immutabledb_open/in_memory/50000
Benchmarking scaling/immutabledb_open/in_memory/50000: Warming up for 3.0000 s
Benchmarking scaling/immutabledb_open/in_memory/50000: Collecting 10 samples in estimated 5.6054 s (20 iterations)
Benchmarking scaling/immutabledb_open/in_memory/50000: Analyzing
scaling/immutabledb_open/in_memory/50000
time: [277.88 ms 281.27 ms 285.05 ms]
Benchmarking scaling/immutabledb_open/mmap_cached/50000
Benchmarking scaling/immutabledb_open/mmap_cached/50000: Warming up for 3.0000 s
Benchmarking scaling/immutabledb_open/mmap_cached/50000: Collecting 10 samples in estimated 5.4234 s (20 iterations)
Benchmarking scaling/immutabledb_open/mmap_cached/50000: Analyzing
scaling/immutabledb_open/mmap_cached/50000
time: [273.54 ms 278.20 ms 283.10 ms]
Benchmarking scaling/immutabledb_open/in_memory/100000
Benchmarking scaling/immutabledb_open/in_memory/100000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 7.1s.
Benchmarking scaling/immutabledb_open/in_memory/100000: Collecting 10 samples in estimated 7.1093 s (10 iterations)
Benchmarking scaling/immutabledb_open/in_memory/100000: Analyzing
scaling/immutabledb_open/in_memory/100000
time: [544.35 ms 548.50 ms 552.43 ms]
Benchmarking scaling/immutabledb_open/mmap_cached/100000
Benchmarking scaling/immutabledb_open/mmap_cached/100000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 5.4s.
Benchmarking scaling/immutabledb_open/mmap_cached/100000: Collecting 10 samples in estimated 5.3628 s (10 iterations)
Benchmarking scaling/immutabledb_open/mmap_cached/100000: Analyzing
scaling/immutabledb_open/mmap_cached/100000
time: [534.53 ms 537.62 ms 540.75 ms]
Benchmarking scaling/chaindb_insert/default_20kb/10000
Benchmarking scaling/chaindb_insert/default_20kb/10000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 8.8s or enable flat sampling.
Benchmarking scaling/chaindb_insert/default_20kb/10000: Collecting 10 samples in estimated 8.8042 s (55 iterations)
Benchmarking scaling/chaindb_insert/default_20kb/10000: Analyzing
scaling/chaindb_insert/default_20kb/10000
time: [159.18 ms 159.68 ms 160.00 ms]
Ledger (UTxO)
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking utxo_store/insert/default/1000000
Benchmarking utxo_store/insert/default/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 28.3s.
Benchmarking utxo_store/insert/default/1000000: Collecting 10 samples in estimated 28.330 s (10 iterations)
Benchmarking utxo_store/insert/default/1000000: Analyzing
utxo_store/insert/default/1000000
time: [2.7960 s 2.8116 s 2.8302 s]
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high mild
Benchmarking utxo_store/lookup/hit/1000000
Benchmarking utxo_store/lookup/hit/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup/hit/1000000: Collecting 100 samples in estimated 5.9231 s (10k iterations)
Benchmarking utxo_store/lookup/hit/1000000: Analyzing
utxo_store/lookup/hit/1000000
time: [583.39 µs 584.93 µs 586.69 µs]
Found 5 outliers among 100 measurements (5.00%)
4 (4.00%) high mild
1 (1.00%) high severe
Benchmarking utxo_store/lookup/miss/1000000
Benchmarking utxo_store/lookup/miss/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup/miss/1000000: Collecting 100 samples in estimated 5.2880 s (15k iterations)
Benchmarking utxo_store/lookup/miss/1000000: Analyzing
utxo_store/lookup/miss/1000000
time: [349.19 µs 349.52 µs 349.89 µs]
Found 6 outliers among 100 measurements (6.00%)
1 (1.00%) low mild
2 (2.00%) high mild
3 (3.00%) high severe
Benchmarking utxo_store/contains/hit
Benchmarking utxo_store/contains/hit: Warming up for 3.0000 s
Benchmarking utxo_store/contains/hit: Collecting 100 samples in estimated 7.0610 s (15k iterations)
Benchmarking utxo_store/contains/hit: Analyzing
utxo_store/contains/hit time: [466.11 µs 467.01 µs 467.98 µs]
Found 9 outliers among 100 measurements (9.00%)
1 (1.00%) low mild
6 (6.00%) high mild
2 (2.00%) high severe
Benchmarking utxo_store/contains/miss
Benchmarking utxo_store/contains/miss: Warming up for 3.0000 s
Benchmarking utxo_store/contains/miss: Collecting 100 samples in estimated 5.2092 s (15k iterations)
Benchmarking utxo_store/contains/miss: Analyzing
utxo_store/contains/miss
time: [342.31 µs 342.78 µs 343.28 µs]
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe
Benchmarking utxo_store/remove/sequential/1000000
Benchmarking utxo_store/remove/sequential/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 54.0s.
Benchmarking utxo_store/remove/sequential/1000000: Collecting 10 samples in estimated 54.004 s (10 iterations)
Benchmarking utxo_store/remove/sequential/1000000: Analyzing
utxo_store/remove/sequential/1000000
time: [2.8775 s 2.8868 s 2.8966 s]
Benchmarking utxo_store/apply_tx/block_50tx_3in_2out
Benchmarking utxo_store/apply_tx/block_50tx_3in_2out: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 29.5s.
Benchmarking utxo_store/apply_tx/block_50tx_3in_2out: Collecting 10 samples in estimated 29.451 s (10 iterations)
Benchmarking utxo_store/apply_tx/block_50tx_3in_2out: Analyzing
utxo_store/apply_tx/block_50tx_3in_2out
time: [372.38 ms 378.06 ms 384.21 ms]
Benchmarking utxo_store/apply_tx/block_300tx_2in_2out
Benchmarking utxo_store/apply_tx/block_300tx_2in_2out: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 28.4s.
Benchmarking utxo_store/apply_tx/block_300tx_2in_2out: Collecting 10 samples in estimated 28.446 s (10 iterations)
Benchmarking utxo_store/apply_tx/block_300tx_2in_2out: Analyzing
utxo_store/apply_tx/block_300tx_2in_2out
time: [359.74 ms 363.57 ms 366.98 ms]
Benchmarking utxo_store/multi_asset/insert_mixed_30pct/1000000
Benchmarking utxo_store/multi_asset/insert_mixed_30pct/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 38.7s.
Benchmarking utxo_store/multi_asset/insert_mixed_30pct/1000000: Collecting 10 samples in estimated 38.729 s (10 iterations)
Benchmarking utxo_store/multi_asset/insert_mixed_30pct/1000000: Analyzing
utxo_store/multi_asset/insert_mixed_30pct/1000000
time: [3.5484 s 3.5731 s 3.6006 s]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
Benchmarking utxo_store/multi_asset/lookup_mixed_30pct/1000000
Benchmarking utxo_store/multi_asset/lookup_mixed_30pct/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 35.7s.
Benchmarking utxo_store/multi_asset/lookup_mixed_30pct/1000000: Collecting 10 samples in estimated 35.654 s (10 iterations)
Benchmarking utxo_store/multi_asset/lookup_mixed_30pct/1000000: Analyzing
utxo_store/multi_asset/lookup_mixed_30pct/1000000
time: [124.74 ms 133.38 ms 141.55 ms]
Benchmarking utxo_store/total_lovelace/scan/1000000
Benchmarking utxo_store/total_lovelace/scan/1000000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 29.0s, or reduce sample count to 10.
Benchmarking utxo_store/total_lovelace/scan/1000000: Collecting 100 samples in estimated 29.030 s (100 iterations)
Benchmarking utxo_store/total_lovelace/scan/1000000: Analyzing
utxo_store/total_lovelace/scan/1000000
time: [279.56 ms 280.18 ms 280.82 ms]
Found 5 outliers among 100 measurements (5.00%)
5 (5.00%) high mild
Benchmarking utxo_store/rebuild_address_index/rebuild/1000000
Benchmarking utxo_store/rebuild_address_index/rebuild/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 36.9s.
Benchmarking utxo_store/rebuild_address_index/rebuild/1000000: Collecting 10 samples in estimated 36.857 s (10 iterations)
Benchmarking utxo_store/rebuild_address_index/rebuild/1000000: Analyzing
utxo_store/rebuild_address_index/rebuild/1000000
time: [502.21 ms 505.66 ms 509.51 ms]
Benchmarking utxo_store/insert_configs/low_8gb/1000000
Benchmarking utxo_store/insert_configs/low_8gb/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 27.1s.
Benchmarking utxo_store/insert_configs/low_8gb/1000000: Collecting 10 samples in estimated 27.115 s (10 iterations)
Benchmarking utxo_store/insert_configs/low_8gb/1000000: Analyzing
utxo_store/insert_configs/low_8gb/1000000
time: [2.6711 s 2.6786 s 2.6868 s]
Benchmarking utxo_store/insert_configs/mid_16gb/1000000
Benchmarking utxo_store/insert_configs/mid_16gb/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.4s.
Benchmarking utxo_store/insert_configs/mid_16gb/1000000: Collecting 10 samples in estimated 26.373 s (10 iterations)
Benchmarking utxo_store/insert_configs/mid_16gb/1000000: Analyzing
utxo_store/insert_configs/mid_16gb/1000000
time: [2.6345 s 2.6422 s 2.6511 s]
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high mild
Benchmarking utxo_store/insert_configs/high_32gb/1000000
Benchmarking utxo_store/insert_configs/high_32gb/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.3s.
Benchmarking utxo_store/insert_configs/high_32gb/1000000: Collecting 10 samples in estimated 26.347 s (10 iterations)
Benchmarking utxo_store/insert_configs/high_32gb/1000000: Analyzing
utxo_store/insert_configs/high_32gb/1000000
time: [2.6234 s 2.6291 s 2.6354 s]
Benchmarking utxo_store/insert_configs/high_bloom_16gb/1000000
Benchmarking utxo_store/insert_configs/high_bloom_16gb/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.8s.
Benchmarking utxo_store/insert_configs/high_bloom_16gb/1000000: Collecting 10 samples in estimated 26.839 s (10 iterations)
Benchmarking utxo_store/insert_configs/high_bloom_16gb/1000000: Analyzing
utxo_store/insert_configs/high_bloom_16gb/1000000
time: [2.6720 s 2.7026 s 2.7326 s]
Benchmarking utxo_store/insert_configs/legacy_small/1000000
Benchmarking utxo_store/insert_configs/legacy_small/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.5s.
Benchmarking utxo_store/insert_configs/legacy_small/1000000: Collecting 10 samples in estimated 26.478 s (10 iterations)
Benchmarking utxo_store/insert_configs/legacy_small/1000000: Analyzing
utxo_store/insert_configs/legacy_small/1000000
time: [2.6395 s 2.6457 s 2.6519 s]
Benchmarking utxo_store/lookup_configs/low_8gb/1000000
Benchmarking utxo_store/lookup_configs/low_8gb/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup_configs/low_8gb/1000000: Collecting 100 samples in estimated 6.7520 s (15k iterations)
Benchmarking utxo_store/lookup_configs/low_8gb/1000000: Analyzing
utxo_store/lookup_configs/low_8gb/1000000
time: [445.95 µs 446.62 µs 447.46 µs]
Found 8 outliers among 100 measurements (8.00%)
1 (1.00%) high mild
7 (7.00%) high severe
Benchmarking utxo_store/lookup_configs/mid_16gb/1000000
Benchmarking utxo_store/lookup_configs/mid_16gb/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup_configs/mid_16gb/1000000: Collecting 100 samples in estimated 6.7223 s (15k iterations)
Benchmarking utxo_store/lookup_configs/mid_16gb/1000000: Analyzing
utxo_store/lookup_configs/mid_16gb/1000000
time: [444.37 µs 444.77 µs 445.20 µs]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking utxo_store/lookup_configs/high_32gb/1000000
Benchmarking utxo_store/lookup_configs/high_32gb/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup_configs/high_32gb/1000000: Collecting 100 samples in estimated 6.7214 s (15k iterations)
Benchmarking utxo_store/lookup_configs/high_32gb/1000000: Analyzing
utxo_store/lookup_configs/high_32gb/1000000
time: [445.93 µs 446.25 µs 446.58 µs]
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe
Benchmarking utxo_store/lookup_configs/high_bloom_16gb/1000000
Benchmarking utxo_store/lookup_configs/high_bloom_16gb/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup_configs/high_bloom_16gb/1000000: Collecting 100 samples in estimated 6.7187 s (15k iterations)
Benchmarking utxo_store/lookup_configs/high_bloom_16gb/1000000: Analyzing
utxo_store/lookup_configs/high_bloom_16gb/1000000
time: [441.40 µs 441.87 µs 442.48 µs]
Found 4 outliers among 100 measurements (4.00%)
2 (2.00%) high mild
2 (2.00%) high severe
Benchmarking utxo_store/lookup_configs/legacy_small/1000000
Benchmarking utxo_store/lookup_configs/legacy_small/1000000: Warming up for 3.0000 s
Benchmarking utxo_store/lookup_configs/legacy_small/1000000: Collecting 100 samples in estimated 6.6510 s (15k iterations)
Benchmarking utxo_store/lookup_configs/legacy_small/1000000: Analyzing
utxo_store/lookup_configs/legacy_small/1000000
time: [439.09 µs 439.42 µs 439.78 µs]
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe
Benchmarking utxo_scaling/insert/default/100000
Benchmarking utxo_scaling/insert/default/100000: Warming up for 3.0000 s
Benchmarking utxo_scaling/insert/default/100000: Collecting 10 samples in estimated 6.5048 s (30 iterations)
Benchmarking utxo_scaling/insert/default/100000: Analyzing
utxo_scaling/insert/default/100000
time: [214.92 ms 215.67 ms 216.45 ms]
Benchmarking utxo_scaling/insert/default/500000
Benchmarking utxo_scaling/insert/default/500000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 12.2s.
Benchmarking utxo_scaling/insert/default/500000: Collecting 10 samples in estimated 12.184 s (10 iterations)
Benchmarking utxo_scaling/insert/default/500000: Analyzing
utxo_scaling/insert/default/500000
time: [1.2162 s 1.2235 s 1.2305 s]
Benchmarking utxo_scaling/insert/default/1000000
Benchmarking utxo_scaling/insert/default/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.5s.
Benchmarking utxo_scaling/insert/default/1000000: Collecting 10 samples in estimated 26.544 s (10 iterations)
Benchmarking utxo_scaling/insert/default/1000000: Analyzing
utxo_scaling/insert/default/1000000
time: [2.6159 s 2.6218 s 2.6279 s]
Benchmarking utxo_scaling/lookup/hit/100000
Benchmarking utxo_scaling/lookup/hit/100000: Warming up for 3.0000 s
Benchmarking utxo_scaling/lookup/hit/100000: Collecting 10 samples in estimated 5.0141 s (14k iterations)
Benchmarking utxo_scaling/lookup/hit/100000: Analyzing
utxo_scaling/lookup/hit/100000
time: [358.58 µs 359.07 µs 360.00 µs]
Benchmarking utxo_scaling/lookup/hit/500000
Benchmarking utxo_scaling/lookup/hit/500000: Warming up for 3.0000 s
Benchmarking utxo_scaling/lookup/hit/500000: Collecting 10 samples in estimated 5.0127 s (12k iterations)
Benchmarking utxo_scaling/lookup/hit/500000: Analyzing
utxo_scaling/lookup/hit/500000
time: [408.62 µs 409.08 µs 409.56 µs]
Benchmarking utxo_scaling/lookup/hit/1000000
Benchmarking utxo_scaling/lookup/hit/1000000: Warming up for 3.0000 s
Benchmarking utxo_scaling/lookup/hit/1000000: Collecting 10 samples in estimated 5.0076 s (11k iterations)
Benchmarking utxo_scaling/lookup/hit/1000000: Analyzing
utxo_scaling/lookup/hit/1000000
time: [443.45 µs 444.05 µs 445.17 µs]
Found 3 outliers among 10 measurements (30.00%)
1 (10.00%) low mild
2 (20.00%) high severe
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/100000
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/100000: Warming up for 3.0000 s
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/100000: Collecting 10 samples in estimated 6.4542 s (30 iterations)
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/100000: Analyzing
utxo_scaling/apply_tx/block_50tx_3in_2out/100000
time: [11.778 ms 11.948 ms 12.113 ms]
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/500000
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/500000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 12.3s.
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/500000: Collecting 10 samples in estimated 12.292 s (10 iterations)
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/500000: Analyzing
utxo_scaling/apply_tx/block_50tx_3in_2out/500000
time: [117.30 ms 124.30 ms 130.92 ms]
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/1000000
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/1000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 26.2s.
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/1000000: Collecting 10 samples in estimated 26.215 s (10 iterations)
Benchmarking utxo_scaling/apply_tx/block_50tx_3in_2out/1000000: Analyzing
utxo_scaling/apply_tx/block_50tx_3in_2out/1000000
time: [272.44 ms 276.35 ms 280.04 ms]
Benchmarking utxo_scaling/total_lovelace/scan/100000
Benchmarking utxo_scaling/total_lovelace/scan/100000: Warming up for 3.0000 s
Benchmarking utxo_scaling/total_lovelace/scan/100000: Collecting 10 samples in estimated 5.3183 s (220 iterations)
Benchmarking utxo_scaling/total_lovelace/scan/100000: Analyzing
utxo_scaling/total_lovelace/scan/100000
time: [23.042 ms 23.196 ms 23.413 ms]
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high mild
Benchmarking utxo_scaling/total_lovelace/scan/500000
Benchmarking utxo_scaling/total_lovelace/scan/500000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 7.5s or enable flat sampling.
Benchmarking utxo_scaling/total_lovelace/scan/500000: Collecting 10 samples in estimated 7.5370 s (55 iterations)
Benchmarking utxo_scaling/total_lovelace/scan/500000: Analyzing
utxo_scaling/total_lovelace/scan/500000
time: [134.37 ms 134.43 ms 134.55 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) low severe
Benchmarking utxo_scaling/total_lovelace/scan/1000000
Benchmarking utxo_scaling/total_lovelace/scan/1000000: Warming up for 3.0000 s
Benchmarking utxo_scaling/total_lovelace/scan/1000000: Collecting 10 samples in estimated 5.5806 s (20 iterations)
Benchmarking utxo_scaling/total_lovelace/scan/1000000: Analyzing
utxo_scaling/total_lovelace/scan/1000000
time: [273.16 ms 273.40 ms 273.54 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) low severe
Benchmarking utxo_large_scale/insert/default/5000000
Benchmarking utxo_large_scale/insert/default/5000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 156.6s.
Benchmarking utxo_large_scale/insert/default/5000000: Collecting 10 samples in estimated 156.62 s (10 iterations)
Benchmarking utxo_large_scale/insert/default/5000000: Analyzing
utxo_large_scale/insert/default/5000000
time: [15.711 s 15.789 s 15.865 s]
Benchmarking utxo_large_scale/insert/default/10000000
Benchmarking utxo_large_scale/insert/default/10000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 352.0s.
Benchmarking utxo_large_scale/insert/default/10000000: Collecting 10 samples in estimated 352.00 s (10 iterations)
Benchmarking utxo_large_scale/insert/default/10000000: Analyzing
utxo_large_scale/insert/default/10000000
time: [34.438 s 34.784 s 35.110 s]
Benchmarking utxo_large_scale/lookup/hit/5000000
Benchmarking utxo_large_scale/lookup/hit/5000000: Warming up for 3.0000 s
Benchmarking utxo_large_scale/lookup/hit/5000000: Collecting 10 samples in estimated 5.0177 s (3410 iterations)
Benchmarking utxo_large_scale/lookup/hit/5000000: Analyzing
utxo_large_scale/lookup/hit/5000000
time: [1.4426 ms 1.4808 ms 1.5136 ms]
Benchmarking utxo_large_scale/lookup/hit/10000000
Benchmarking utxo_large_scale/lookup/hit/10000000: Warming up for 3.0000 s
Benchmarking utxo_large_scale/lookup/hit/10000000: Collecting 10 samples in estimated 5.1001 s (2695 iterations)
Benchmarking utxo_large_scale/lookup/hit/10000000: Analyzing
utxo_large_scale/lookup/hit/10000000
time: [1.7652 ms 1.8074 ms 1.8388 ms]
Benchmarking utxo_large_scale/total_lovelace/scan/5000000
Benchmarking utxo_large_scale/total_lovelace/scan/5000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 16.3s.
Benchmarking utxo_large_scale/total_lovelace/scan/5000000: Collecting 10 samples in estimated 16.251 s (10 iterations)
Benchmarking utxo_large_scale/total_lovelace/scan/5000000: Analyzing
utxo_large_scale/total_lovelace/scan/5000000
time: [1.6079 s 1.6117 s 1.6167 s]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe
Benchmarking utxo_large_scale/total_lovelace/scan/10000000
Benchmarking utxo_large_scale/total_lovelace/scan/10000000: Warming up for 3.0000 s
Warning: Unable to complete 10 samples in 5.0s. You may wish to increase target time to 31.4s.
Benchmarking utxo_large_scale/total_lovelace/scan/10000000: Collecting 10 samples in estimated 31.359 s (10 iterations)
Benchmarking utxo_large_scale/total_lovelace/scan/10000000: Analyzing
utxo_large_scale/total_lovelace/scan/10000000
time: [3.1513 s 3.1614 s 3.1715 s]
Benchmarking ledger/apply_block/apply_only_shelley_50tx
Benchmarking ledger/apply_block/apply_only_shelley_50tx: Warming up for 3.0000 s
Benchmarking ledger/apply_block/apply_only_shelley_50tx: Collecting 20 samples in estimated 5.0363 s (13k iterations)
Benchmarking ledger/apply_block/apply_only_shelley_50tx: Analyzing
ledger/apply_block/apply_only_shelley_50tx
time: [390.77 µs 396.08 µs 403.38 µs]
Found 4 outliers among 20 measurements (20.00%)
3 (15.00%) low severe
1 (5.00%) high severe
Benchmarking ledger/apply_block/validate_all_shelley_50tx
Benchmarking ledger/apply_block/validate_all_shelley_50tx: Warming up for 3.0000 s
Benchmarking ledger/apply_block/validate_all_shelley_50tx: Collecting 20 samples in estimated 5.0692 s (11k iterations)
Benchmarking ledger/apply_block/validate_all_shelley_50tx: Analyzing
ledger/apply_block/validate_all_shelley_50tx
time: [454.88 µs 458.00 µs 462.24 µs]
Found 4 outliers among 20 measurements (20.00%)
3 (15.00%) low severe
1 (5.00%) high severe
Network
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking network/handshake_encode/n2n_version_data
Benchmarking network/handshake_encode/n2n_version_data: Warming up for 3.0000 s
Benchmarking network/handshake_encode/n2n_version_data: Collecting 100 samples in estimated 5.0001 s (203M iterations)
Benchmarking network/handshake_encode/n2n_version_data: Analyzing
network/handshake_encode/n2n_version_data
time: [24.096 ns 24.280 ns 24.516 ns]
Found 8 outliers among 100 measurements (8.00%)
2 (2.00%) high mild
6 (6.00%) high severe
Benchmarking network/handshake_encode/n2c_version_data
Benchmarking network/handshake_encode/n2c_version_data: Warming up for 3.0000 s
Benchmarking network/handshake_encode/n2c_version_data: Collecting 100 samples in estimated 5.0001 s (286M iterations)
Benchmarking network/handshake_encode/n2c_version_data: Analyzing
network/handshake_encode/n2c_version_data
time: [17.809 ns 18.181 ns 18.571 ns]
Found 16 outliers among 100 measurements (16.00%)
5 (5.00%) high mild
11 (11.00%) high severe
Benchmarking network/chainsync/roll_forward/encode/256
Benchmarking network/chainsync/roll_forward/encode/256: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/encode/256: Collecting 100 samples in estimated 5.0001 s (59M iterations)
Benchmarking network/chainsync/roll_forward/encode/256: Analyzing
network/chainsync/roll_forward/encode/256
time: [85.235 ns 85.852 ns 86.652 ns]
thrpt: [3.3426 GiB/s 3.3737 GiB/s 3.3982 GiB/s]
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high severe
Benchmarking network/chainsync/roll_forward/decode/256
Benchmarking network/chainsync/roll_forward/decode/256: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/decode/256: Collecting 100 samples in estimated 5.0002 s (82M iterations)
Benchmarking network/chainsync/roll_forward/decode/256: Analyzing
network/chainsync/roll_forward/decode/256
time: [59.040 ns 59.733 ns 60.537 ns]
thrpt: [4.7845 GiB/s 4.8489 GiB/s 4.9058 GiB/s]
Found 10 outliers among 100 measurements (10.00%)
3 (3.00%) high mild
7 (7.00%) high severe
Benchmarking network/chainsync/roll_forward/encode/1024
Benchmarking network/chainsync/roll_forward/encode/1024: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/encode/1024: Collecting 100 samples in estimated 5.0002 s (49M iterations)
Benchmarking network/chainsync/roll_forward/encode/1024: Analyzing
network/chainsync/roll_forward/encode/1024
time: [100.87 ns 101.63 ns 102.65 ns]
thrpt: [9.7893 GiB/s 9.8876 GiB/s 9.9624 GiB/s]
Found 13 outliers among 100 measurements (13.00%)
4 (4.00%) high mild
9 (9.00%) high severe
Benchmarking network/chainsync/roll_forward/decode/1024
Benchmarking network/chainsync/roll_forward/decode/1024: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/decode/1024: Collecting 100 samples in estimated 5.0002 s (83M iterations)
Benchmarking network/chainsync/roll_forward/decode/1024: Analyzing
network/chainsync/roll_forward/decode/1024
time: [59.604 ns 60.201 ns 61.044 ns]
thrpt: [16.462 GiB/s 16.692 GiB/s 16.859 GiB/s]
Found 12 outliers among 100 measurements (12.00%)
6 (6.00%) high mild
6 (6.00%) high severe
Benchmarking network/chainsync/roll_forward/encode/4096
Benchmarking network/chainsync/roll_forward/encode/4096: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/encode/4096: Collecting 100 samples in estimated 5.0001 s (47M iterations)
Benchmarking network/chainsync/roll_forward/encode/4096: Analyzing
network/chainsync/roll_forward/encode/4096
time: [104.62 ns 105.51 ns 106.67 ns]
thrpt: [36.241 GiB/s 36.640 GiB/s 36.951 GiB/s]
Found 14 outliers among 100 measurements (14.00%)
1 (1.00%) high mild
13 (13.00%) high severe
Benchmarking network/chainsync/roll_forward/decode/4096
Benchmarking network/chainsync/roll_forward/decode/4096: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_forward/decode/4096: Collecting 100 samples in estimated 5.0000 s (54M iterations)
Benchmarking network/chainsync/roll_forward/decode/4096: Analyzing
network/chainsync/roll_forward/decode/4096
time: [91.204 ns 91.360 ns 91.536 ns]
thrpt: [42.234 GiB/s 42.315 GiB/s 42.388 GiB/s]
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe
Benchmarking network/chainsync/roll_backward/encode
Benchmarking network/chainsync/roll_backward/encode: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_backward/encode: Collecting 100 samples in estimated 5.0002 s (39M iterations)
Benchmarking network/chainsync/roll_backward/encode: Analyzing
network/chainsync/roll_backward/encode
time: [131.92 ns 132.32 ns 132.67 ns]
thrpt: [632.58 MiB/s 634.25 MiB/s 636.15 MiB/s]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking network/chainsync/roll_backward/decode
Benchmarking network/chainsync/roll_backward/decode: Warming up for 3.0000 s
Benchmarking network/chainsync/roll_backward/decode: Collecting 100 samples in estimated 5.0000 s (124M iterations)
Benchmarking network/chainsync/roll_backward/decode: Analyzing
network/chainsync/roll_backward/decode
time: [38.852 ns 38.927 ns 39.040 ns]
thrpt: [2.0993 GiB/s 2.1054 GiB/s 2.1094 GiB/s]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
Benchmarking network/blockfetch/msg_block/encode/2048
Benchmarking network/blockfetch/msg_block/encode/2048: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/encode/2048: Collecting 100 samples in estimated 5.0002 s (78M iterations)
Benchmarking network/blockfetch/msg_block/encode/2048: Analyzing
network/blockfetch/msg_block/encode/2048
time: [63.677 ns 63.714 ns 63.753 ns]
thrpt: [29.991 GiB/s 30.009 GiB/s 30.027 GiB/s]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
Benchmarking network/blockfetch/msg_block/decode/2048
Benchmarking network/blockfetch/msg_block/decode/2048: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/decode/2048: Collecting 100 samples in estimated 5.0000 s (80M iterations)
Benchmarking network/blockfetch/msg_block/decode/2048: Analyzing
network/blockfetch/msg_block/decode/2048
time: [62.485 ns 62.579 ns 62.703 ns]
thrpt: [30.493 GiB/s 30.553 GiB/s 30.599 GiB/s]
Benchmarking network/blockfetch/msg_block/encode/20480
Benchmarking network/blockfetch/msg_block/encode/20480: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/encode/20480: Collecting 100 samples in estimated 5.0021 s (10M iterations)
Benchmarking network/blockfetch/msg_block/encode/20480: Analyzing
network/blockfetch/msg_block/encode/20480
time: [494.80 ns 502.10 ns 509.48 ns]
thrpt: [37.446 GiB/s 37.997 GiB/s 38.557 GiB/s]
Found 21 outliers among 100 measurements (21.00%)
4 (4.00%) high mild
17 (17.00%) high severe
Benchmarking network/blockfetch/msg_block/decode/20480
Benchmarking network/blockfetch/msg_block/decode/20480: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/decode/20480: Collecting 100 samples in estimated 5.0001 s (27M iterations)
Benchmarking network/blockfetch/msg_block/decode/20480: Analyzing
network/blockfetch/msg_block/decode/20480
time: [182.80 ns 186.04 ns 189.45 ns]
thrpt: [100.70 GiB/s 102.55 GiB/s 104.37 GiB/s]
Found 9 outliers among 100 measurements (9.00%)
7 (7.00%) high mild
2 (2.00%) high severe
Benchmarking network/blockfetch/msg_block/encode/90000
Benchmarking network/blockfetch/msg_block/encode/90000: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/encode/90000: Collecting 100 samples in estimated 5.0098 s (2.6M iterations)
Benchmarking network/blockfetch/msg_block/encode/90000: Analyzing
network/blockfetch/msg_block/encode/90000
time: [1.9535 µs 1.9558 µs 1.9600 µs]
thrpt: [42.769 GiB/s 42.860 GiB/s 42.911 GiB/s]
Found 16 outliers among 100 measurements (16.00%)
5 (5.00%) high mild
11 (11.00%) high severe
Benchmarking network/blockfetch/msg_block/decode/90000
Benchmarking network/blockfetch/msg_block/decode/90000: Warming up for 3.0000 s
Benchmarking network/blockfetch/msg_block/decode/90000: Collecting 100 samples in estimated 5.0070 s (2.6M iterations)
Benchmarking network/blockfetch/msg_block/decode/90000: Analyzing
network/blockfetch/msg_block/decode/90000
time: [1.9608 µs 1.9698 µs 1.9838 µs]
thrpt: [42.255 GiB/s 42.555 GiB/s 42.751 GiB/s]
Found 11 outliers among 100 measurements (11.00%)
3 (3.00%) high mild
8 (8.00%) high severe
Benchmarking network/blockfetch/request_range/encode
Benchmarking network/blockfetch/request_range/encode: Warming up for 3.0000 s
Benchmarking network/blockfetch/request_range/encode: Collecting 100 samples in estimated 5.0001 s (61M iterations)
Benchmarking network/blockfetch/request_range/encode: Analyzing
network/blockfetch/request_range/encode
time: [85.836 ns 86.813 ns 87.718 ns]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking network/blockfetch/request_range/decode
Benchmarking network/blockfetch/request_range/decode: Warming up for 3.0000 s
Benchmarking network/blockfetch/request_range/decode: Collecting 100 samples in estimated 5.0000 s (130M iterations)
Benchmarking network/blockfetch/request_range/decode: Analyzing
network/blockfetch/request_range/decode
time: [37.687 ns 37.936 ns 38.257 ns]
Found 5 outliers among 100 measurements (5.00%)
2 (2.00%) high mild
3 (3.00%) high severe
Benchmarking network/n2c_query_encode/pparams/hfc_success
Benchmarking network/n2c_query_encode/pparams/hfc_success: Warming up for 3.0000 s
Benchmarking network/n2c_query_encode/pparams/hfc_success: Collecting 100 samples in estimated 5.0002 s (109M iterations)
Benchmarking network/n2c_query_encode/pparams/hfc_success: Analyzing
network/n2c_query_encode/pparams/hfc_success
time: [45.917 ns 46.235 ns 46.679 ns]
thrpt: [1.8156 GiB/s 1.8330 GiB/s 1.8457 GiB/s]
Found 16 outliers among 100 measurements (16.00%)
1 (1.00%) high mild
15 (15.00%) high severe
Benchmarking network/n2c_query_encode/pparams/tag24
Benchmarking network/n2c_query_encode/pparams/tag24: Warming up for 3.0000 s
Benchmarking network/n2c_query_encode/pparams/tag24: Collecting 100 samples in estimated 5.0000 s (107M iterations)
Benchmarking network/n2c_query_encode/pparams/tag24: Analyzing
network/n2c_query_encode/pparams/tag24
time: [46.863 ns 46.910 ns 46.963 ns]
thrpt: [1.8046 GiB/s 1.8067 GiB/s 1.8085 GiB/s]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
Benchmarking network/n2c_query_encode/govstate/hfc_success
Benchmarking network/n2c_query_encode/govstate/hfc_success: Warming up for 3.0000 s
Benchmarking network/n2c_query_encode/govstate/hfc_success: Collecting 100 samples in estimated 5.0002 s (102M iterations)
Benchmarking network/n2c_query_encode/govstate/hfc_success: Analyzing
network/n2c_query_encode/govstate/hfc_success
time: [49.809 ns 50.292 ns 51.000 ns]
thrpt: [14.280 GiB/s 14.481 GiB/s 14.622 GiB/s]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking network/n2c_query_encode/govstate/tag24
Benchmarking network/n2c_query_encode/govstate/tag24: Warming up for 3.0000 s
Benchmarking network/n2c_query_encode/govstate/tag24: Collecting 100 samples in estimated 5.0003 s (80M iterations)
Benchmarking network/n2c_query_encode/govstate/tag24: Analyzing
network/n2c_query_encode/govstate/tag24
time: [60.939 ns 61.332 ns 61.873 ns]
thrpt: [11.771 GiB/s 11.875 GiB/s 11.951 GiB/s]
Found 5 outliers among 100 measurements (5.00%)
5 (5.00%) high severe
Benchmarking network/n2c_query_encode/era_mismatch
Benchmarking network/n2c_query_encode/era_mismatch: Warming up for 3.0000 s
Benchmarking network/n2c_query_encode/era_mismatch: Collecting 100 samples in estimated 5.0000 s (286M iterations)
Benchmarking network/n2c_query_encode/era_mismatch: Analyzing
network/n2c_query_encode/era_mismatch
time: [17.557 ns 17.640 ns 17.749 ns]
thrpt: [41.034 GiB/s 41.286 GiB/s 41.482 GiB/s]
Found 8 outliers among 100 measurements (8.00%)
3 (3.00%) high mild
5 (5.00%) high severe
Consensus
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking consensus/vrf_leader_check/single/sigma=0.0000247
Benchmarking consensus/vrf_leader_check/single/sigma=0.0000247: Warming up for 3.0000 s
Benchmarking consensus/vrf_leader_check/single/sigma=0.0000247: Collecting 100 samples in estimated 5.1097 s (131k iterations)
Benchmarking consensus/vrf_leader_check/single/sigma=0.0000247: Analyzing
consensus/vrf_leader_check/single/sigma=0.0000247
time: [39.074 µs 39.107 µs 39.142 µs]
Found 4 outliers among 100 measurements (4.00%)
3 (3.00%) high mild
1 (1.00%) high severe
Benchmarking consensus/vrf_leader_check/single/sigma=0.001
Benchmarking consensus/vrf_leader_check/single/sigma=0.001: Warming up for 3.0000 s
Benchmarking consensus/vrf_leader_check/single/sigma=0.001: Collecting 100 samples in estimated 5.1229 s (131k iterations)
Benchmarking consensus/vrf_leader_check/single/sigma=0.001: Analyzing
consensus/vrf_leader_check/single/sigma=0.001
time: [38.991 µs 39.050 µs 39.129 µs]
Found 9 outliers among 100 measurements (9.00%)
4 (4.00%) high mild
5 (5.00%) high severe
Benchmarking consensus/vrf_leader_check/single/sigma=0.01
Benchmarking consensus/vrf_leader_check/single/sigma=0.01: Warming up for 3.0000 s
Benchmarking consensus/vrf_leader_check/single/sigma=0.01: Collecting 100 samples in estimated 5.1633 s (131k iterations)
Benchmarking consensus/vrf_leader_check/single/sigma=0.01: Analyzing
consensus/vrf_leader_check/single/sigma=0.01
time: [38.990 µs 39.160 µs 39.471 µs]
Found 8 outliers among 100 measurements (8.00%)
2 (2.00%) high mild
6 (6.00%) high severe
Benchmarking consensus/vrf_leader_check/batch/21600
Benchmarking consensus/vrf_leader_check/batch/21600: Warming up for 3.0000 s
Warning: Unable to complete 20 samples in 5.0s. You may wish to increase target time to 17.0s, or reduce sample count to 10.
Benchmarking consensus/vrf_leader_check/batch/21600: Collecting 20 samples in estimated 16.988 s (20 iterations)
Benchmarking consensus/vrf_leader_check/batch/21600: Analyzing
consensus/vrf_leader_check/batch/21600
time: [840.12 ms 840.77 ms 841.71 ms]
thrpt: [25.662 Kelem/s 25.691 Kelem/s 25.711 Kelem/s]
Found 2 outliers among 20 measurements (10.00%)
2 (10.00%) high severe
Benchmarking consensus/validate_header/replay_mode
Benchmarking consensus/validate_header/replay_mode: Warming up for 3.0000 s
Benchmarking consensus/validate_header/replay_mode: Collecting 100 samples in estimated 5.0000 s (434M iterations)
Benchmarking consensus/validate_header/replay_mode: Analyzing
consensus/validate_header/replay_mode
time: [11.520 ns 11.523 ns 11.527 ns]
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
Benchmarking consensus/chain_selection/longer_fork_100
Benchmarking consensus/chain_selection/longer_fork_100: Warming up for 3.0000 s
Benchmarking consensus/chain_selection/longer_fork_100: Collecting 100 samples in estimated 5.0000 s (3.2B iterations)
Benchmarking consensus/chain_selection/longer_fork_100: Analyzing
consensus/chain_selection/longer_fork_100
time: [1.5558 ns 1.5562 ns 1.5566 ns]
Found 9 outliers among 100 measurements (9.00%)
5 (5.00%) high mild
4 (4.00%) high severe
Benchmarking consensus/chain_selection/equal_length_tiebreak
Benchmarking consensus/chain_selection/equal_length_tiebreak: Warming up for 3.0000 s
Benchmarking consensus/chain_selection/equal_length_tiebreak: Collecting 100 samples in estimated 5.0003 s (15M iterations)
Benchmarking consensus/chain_selection/equal_length_tiebreak: Analyzing
consensus/chain_selection/equal_length_tiebreak
time: [328.62 ns 328.90 ns 329.21 ns]
Found 5 outliers among 100 measurements (5.00%)
4 (4.00%) high mild
1 (1.00%) high severe
Benchmarking consensus/chain_selection/prefer_simple
Benchmarking consensus/chain_selection/prefer_simple: Warming up for 3.0000 s
Benchmarking consensus/chain_selection/prefer_simple: Collecting 100 samples in estimated 5.0000 s (2.1B iterations)
Benchmarking consensus/chain_selection/prefer_simple: Analyzing
consensus/chain_selection/prefer_simple
time: [2.4392 ns 2.4427 ns 2.4460 ns]
LSM
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking lsm/insert/random_keys/10000
Benchmarking lsm/insert/random_keys/10000: Warming up for 3.0000 s
Benchmarking lsm/insert/random_keys/10000: Collecting 10 samples in estimated 5.0036 s (1485 iterations)
Benchmarking lsm/insert/random_keys/10000: Analyzing
lsm/insert/random_keys/10000
time: [3.2380 ms 3.2426 ms 3.2470 ms]
thrpt: [3.0798 Melem/s 3.0839 Melem/s 3.0883 Melem/s]
Benchmarking lsm/point_lookup/hit_random/10000
Benchmarking lsm/point_lookup/hit_random/10000: Warming up for 3.0000 s
Benchmarking lsm/point_lookup/hit_random/10000: Collecting 100 samples in estimated 6.3546 s (15k iterations)
Benchmarking lsm/point_lookup/hit_random/10000: Analyzing
lsm/point_lookup/hit_random/10000
time: [417.83 µs 418.08 µs 418.40 µs]
thrpt: [2.3901 Melem/s 2.3919 Melem/s 2.3933 Melem/s]
Found 9 outliers among 100 measurements (9.00%)
6 (6.00%) high mild
3 (3.00%) high severe
Benchmarking lsm/point_lookup/miss_random/10000
Benchmarking lsm/point_lookup/miss_random/10000: Warming up for 3.0000 s
Benchmarking lsm/point_lookup/miss_random/10000: Collecting 100 samples in estimated 5.4751 s (56k iterations)
Benchmarking lsm/point_lookup/miss_random/10000: Analyzing
lsm/point_lookup/miss_random/10000
time: [98.513 µs 98.802 µs 99.325 µs]
thrpt: [10.068 Melem/s 10.121 Melem/s 10.151 Melem/s]
Found 11 outliers among 100 measurements (11.00%)
2 (2.00%) high mild
9 (9.00%) high severe
Benchmarking lsm/range_scan/window_100_of_10k/10000
Benchmarking lsm/range_scan/window_100_of_10k/10000: Warming up for 3.0000 s
Benchmarking lsm/range_scan/window_100_of_10k/10000: Collecting 20 samples in estimated 5.0009 s (86k iterations)
Benchmarking lsm/range_scan/window_100_of_10k/10000: Analyzing
lsm/range_scan/window_100_of_10k/10000
time: [57.387 µs 57.433 µs 57.485 µs]
thrpt: [1.7396 Melem/s 1.7412 Melem/s 1.7426 Melem/s]
Found 2 outliers among 20 measurements (10.00%)
1 (5.00%) high mild
1 (5.00%) high severe
Benchmarking lsm/range_scan/full_scan/10000
Benchmarking lsm/range_scan/full_scan/10000: Warming up for 3.0000 s
Benchmarking lsm/range_scan/full_scan/10000: Collecting 20 samples in estimated 5.3299 s (3360 iterations)
Benchmarking lsm/range_scan/full_scan/10000: Analyzing
lsm/range_scan/full_scan/10000
time: [1.5689 ms 1.5799 ms 1.5942 ms]
thrpt: [6.2727 Melem/s 6.3293 Melem/s 6.3740 Melem/s]
Found 7 outliers among 20 measurements (35.00%)
2 (10.00%) low severe
2 (10.00%) low mild
1 (5.00%) high mild
2 (10.00%) high severe
Benchmarking lsm/apply_batch/inserts_10k_deletes_2.5k/10000
Benchmarking lsm/apply_batch/inserts_10k_deletes_2.5k/10000: Warming up for 3.0000 s
Benchmarking lsm/apply_batch/inserts_10k_deletes_2.5k/10000: Collecting 10 samples in estimated 5.2395 s (440 iterations)
Benchmarking lsm/apply_batch/inserts_10k_deletes_2.5k/10000: Analyzing
lsm/apply_batch/inserts_10k_deletes_2.5k/10000
time: [5.3623 ms 5.3701 ms 5.3779 ms]
thrpt: [1.8595 Melem/s 1.8621 Melem/s 1.8649 Melem/s]
Benchmarking lsm/snapshot/save_10k
Benchmarking lsm/snapshot/save_10k: Warming up for 3.0000 s
Benchmarking lsm/snapshot/save_10k: Collecting 10 samples in estimated 5.1267 s (770 iterations)
Benchmarking lsm/snapshot/save_10k: Analyzing
lsm/snapshot/save_10k time: [519.31 µs 520.28 µs 522.35 µs]
Benchmarking lsm/snapshot/load_10k
Benchmarking lsm/snapshot/load_10k: Warming up for 3.0000 s
Benchmarking lsm/snapshot/load_10k: Collecting 10 samples in estimated 5.2085 s (660 iterations)
Benchmarking lsm/snapshot/load_10k: Analyzing
lsm/snapshot/load_10k time: [1.5918 ms 1.5997 ms 1.6106 ms]
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high mild
Mempool
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking mempool/add/txs/1000
Benchmarking mempool/add/txs/1000: Warming up for 3.0000 s
Benchmarking mempool/add/txs/1000: Collecting 100 samples in estimated 7.9300 s (10k iterations)
Benchmarking mempool/add/txs/1000: Analyzing
mempool/add/txs/1000 time: [747.97 µs 748.92 µs 749.94 µs]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
Benchmarking mempool/add/txs/5000
Benchmarking mempool/add/txs/5000: Warming up for 3.0000 s
Benchmarking mempool/add/txs/5000: Collecting 100 samples in estimated 5.2894 s (700 iterations)
Benchmarking mempool/add/txs/5000: Analyzing
mempool/add/txs/5000 time: [6.9625 ms 7.2723 ms 7.5749 ms]
Benchmarking mempool/add/txs/10000
Benchmarking mempool/add/txs/10000: Warming up for 3.0000 s
Benchmarking mempool/add/txs/10000: Collecting 100 samples in estimated 5.0733 s (400 iterations)
Benchmarking mempool/add/txs/10000: Analyzing
mempool/add/txs/10000 time: [11.480 ms 11.899 ms 12.357 ms]
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high mild
Benchmarking mempool/remove/txs/1000
Benchmarking mempool/remove/txs/1000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 5.9s, enable flat sampling, or reduce sample count to 60.
Benchmarking mempool/remove/txs/1000: Collecting 100 samples in estimated 5.9211 s (5050 iterations)
Benchmarking mempool/remove/txs/1000: Analyzing
mempool/remove/txs/1000 time: [477.93 µs 479.09 µs 480.32 µs]
Benchmarking mempool/remove/txs/5000
Benchmarking mempool/remove/txs/5000: Warming up for 3.0000 s
Benchmarking mempool/remove/txs/5000: Collecting 100 samples in estimated 5.0552 s (800 iterations)
Benchmarking mempool/remove/txs/5000: Analyzing
mempool/remove/txs/5000 time: [2.7212 ms 2.7317 ms 2.7429 ms]
Found 8 outliers among 100 measurements (8.00%)
7 (7.00%) high mild
1 (1.00%) high severe
Benchmarking mempool/remove/txs/10000
Benchmarking mempool/remove/txs/10000: Warming up for 3.0000 s
Benchmarking mempool/remove/txs/10000: Collecting 100 samples in estimated 5.8452 s (400 iterations)
Benchmarking mempool/remove/txs/10000: Analyzing
mempool/remove/txs/10000
time: [6.1728 ms 6.2121 ms 6.2535 ms]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
Benchmarking mempool/get_sorted/by_fee_density/1000
Benchmarking mempool/get_sorted/by_fee_density/1000: Warming up for 3.0000 s
Benchmarking mempool/get_sorted/by_fee_density/1000: Collecting 100 samples in estimated 5.2993 s (71k iterations)
Benchmarking mempool/get_sorted/by_fee_density/1000: Analyzing
mempool/get_sorted/by_fee_density/1000
time: [74.288 µs 74.470 µs 74.678 µs]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking mempool/get_sorted/by_fee_density/5000
Benchmarking mempool/get_sorted/by_fee_density/5000: Warming up for 3.0000 s
Benchmarking mempool/get_sorted/by_fee_density/5000: Collecting 100 samples in estimated 5.0228 s (56k iterations)
Benchmarking mempool/get_sorted/by_fee_density/5000: Analyzing
mempool/get_sorted/by_fee_density/5000
time: [89.850 µs 90.024 µs 90.226 µs]
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) high mild
3 (3.00%) high severe
Benchmarking mempool/get_sorted/by_fee_density/10000
Benchmarking mempool/get_sorted/by_fee_density/10000: Warming up for 3.0000 s
Benchmarking mempool/get_sorted/by_fee_density/10000: Collecting 100 samples in estimated 5.0361 s (66k iterations)
Benchmarking mempool/get_sorted/by_fee_density/10000: Analyzing
mempool/get_sorted/by_fee_density/10000
time: [75.878 µs 76.022 µs 76.171 µs]
Found 4 outliers among 100 measurements (4.00%)
3 (3.00%) high mild
1 (1.00%) high severe
Benchmarking mempool/drain_readd/txs/1000
Benchmarking mempool/drain_readd/txs/1000: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 7.2s, enable flat sampling, or reduce sample count to 50.
Benchmarking mempool/drain_readd/txs/1000: Collecting 100 samples in estimated 7.2318 s (5050 iterations)
Benchmarking mempool/drain_readd/txs/1000: Analyzing
mempool/drain_readd/txs/1000
time: [758.67 µs 759.91 µs 761.15 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking mempool/drain_readd/txs/5000
Benchmarking mempool/drain_readd/txs/5000: Warming up for 3.0000 s
Benchmarking mempool/drain_readd/txs/5000: Collecting 100 samples in estimated 5.5241 s (700 iterations)
Benchmarking mempool/drain_readd/txs/5000: Analyzing
mempool/drain_readd/txs/5000
time: [4.2867 ms 4.3132 ms 4.3415 ms]
Found 10 outliers among 100 measurements (10.00%)
10 (10.00%) high mild
Benchmarking mempool/drain_readd/txs/10000
Benchmarking mempool/drain_readd/txs/10000: Warming up for 3.0000 s
Benchmarking mempool/drain_readd/txs/10000: Collecting 100 samples in estimated 5.7647 s (200 iterations)
Benchmarking mempool/drain_readd/txs/10000: Analyzing
mempool/drain_readd/txs/10000
time: [13.241 ms 13.333 ms 13.424 ms]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) low mild
1 (1.00%) high mild
Benchmarking mempool/batch_remove/50_from/5000
Benchmarking mempool/batch_remove/50_from/5000: Warming up for 3.0000 s
Benchmarking mempool/batch_remove/50_from/5000: Collecting 100 samples in estimated 5.0009 s (3.4M iterations)
Benchmarking mempool/batch_remove/50_from/5000: Analyzing
mempool/batch_remove/50_from/5000
time: [1.4692 µs 1.4778 µs 1.4910 µs]
Found 8 outliers among 100 measurements (8.00%)
2 (2.00%) high mild
6 (6.00%) high severe
Benchmarking mempool/batch_remove/50_from/10000
Benchmarking mempool/batch_remove/50_from/10000: Warming up for 3.0000 s
Benchmarking mempool/batch_remove/50_from/10000: Collecting 100 samples in estimated 5.0061 s (3.4M iterations)
Benchmarking mempool/batch_remove/50_from/10000: Analyzing
mempool/batch_remove/50_from/10000
time: [1.4798 µs 1.4807 µs 1.4816 µs]
Found 9 outliers among 100 measurements (9.00%)
5 (5.00%) high mild
4 (4.00%) high severe
Crypto
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking ed25519_verify/single
Benchmarking ed25519_verify/single: Warming up for 3.0000 s
Benchmarking ed25519_verify/single: Collecting 100 samples in estimated 5.1711 s (116k iterations)
Benchmarking ed25519_verify/single: Analyzing
ed25519_verify/single time: [44.354 µs 44.451 µs 44.637 µs]
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe
Benchmarking ed25519_batch_verify/sequential/1
Benchmarking ed25519_batch_verify/sequential/1: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/1: Collecting 100 samples in estimated 5.2065 s (106k iterations)
Benchmarking ed25519_batch_verify/sequential/1: Analyzing
ed25519_batch_verify/sequential/1
time: [49.403 µs 49.542 µs 49.688 µs]
Benchmarking ed25519_batch_verify/sequential/10
Benchmarking ed25519_batch_verify/sequential/10: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/10: Collecting 100 samples in estimated 7.3409 s (15k iterations)
Benchmarking ed25519_batch_verify/sequential/10: Analyzing
ed25519_batch_verify/sequential/10
time: [493.16 µs 496.14 µs 500.12 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
Benchmarking ed25519_batch_verify/sequential/50
Benchmarking ed25519_batch_verify/sequential/50: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/50: Collecting 100 samples in estimated 5.1098 s (2100 iterations)
Benchmarking ed25519_batch_verify/sequential/50: Analyzing
ed25519_batch_verify/sequential/50
time: [2.4330 ms 2.4461 ms 2.4589 ms]
Benchmarking ed25519_batch_verify/sequential/100
Benchmarking ed25519_batch_verify/sequential/100: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/100: Collecting 100 samples in estimated 5.3637 s (1100 iterations)
Benchmarking ed25519_batch_verify/sequential/100: Analyzing
ed25519_batch_verify/sequential/100
time: [4.8746 ms 4.9037 ms 4.9334 ms]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking ed25519_batch_verify/sequential/200
Benchmarking ed25519_batch_verify/sequential/200: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/200: Collecting 100 samples in estimated 5.9396 s (600 iterations)
Benchmarking ed25519_batch_verify/sequential/200: Analyzing
ed25519_batch_verify/sequential/200
time: [9.8782 ms 9.9323 ms 9.9848 ms]
Benchmarking ed25519_batch_verify/sequential/500
Benchmarking ed25519_batch_verify/sequential/500: Warming up for 3.0000 s
Benchmarking ed25519_batch_verify/sequential/500: Collecting 100 samples in estimated 7.4432 s (300 iterations)
Benchmarking ed25519_batch_verify/sequential/500: Analyzing
ed25519_batch_verify/sequential/500
time: [24.821 ms 24.960 ms 25.097 ms]
Benchmarking keyhash_from_vkey/single
Benchmarking keyhash_from_vkey/single: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/single: Collecting 100 samples in estimated 5.0008 s (29M iterations)
Benchmarking keyhash_from_vkey/single: Analyzing
keyhash_from_vkey/single
time: [173.37 ns 173.62 ns 173.97 ns]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
Benchmarking keyhash_from_vkey/batch/10
Benchmarking keyhash_from_vkey/batch/10: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/batch/10: Collecting 100 samples in estimated 5.0016 s (3.0M iterations)
Benchmarking keyhash_from_vkey/batch/10: Analyzing
keyhash_from_vkey/batch/10
time: [1.6816 µs 1.6939 µs 1.7055 µs]
Benchmarking keyhash_from_vkey/batch/50
Benchmarking keyhash_from_vkey/batch/50: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/batch/50: Collecting 100 samples in estimated 5.0051 s (606k iterations)
Benchmarking keyhash_from_vkey/batch/50: Analyzing
keyhash_from_vkey/batch/50
time: [8.4052 µs 8.4647 µs 8.5215 µs]
Benchmarking keyhash_from_vkey/batch/100
Benchmarking keyhash_from_vkey/batch/100: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/batch/100: Collecting 100 samples in estimated 5.0070 s (303k iterations)
Benchmarking keyhash_from_vkey/batch/100: Analyzing
keyhash_from_vkey/batch/100
time: [16.809 µs 16.929 µs 17.043 µs]
Benchmarking keyhash_from_vkey/batch/200
Benchmarking keyhash_from_vkey/batch/200: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/batch/200: Collecting 100 samples in estimated 5.0167 s (152k iterations)
Benchmarking keyhash_from_vkey/batch/200: Analyzing
keyhash_from_vkey/batch/200
time: [33.644 µs 33.882 µs 34.108 µs]
Benchmarking keyhash_from_vkey/batch/500
Benchmarking keyhash_from_vkey/batch/500: Warming up for 3.0000 s
Benchmarking keyhash_from_vkey/batch/500: Collecting 100 samples in estimated 5.0051 s (61k iterations)
Benchmarking keyhash_from_vkey/batch/500: Analyzing
keyhash_from_vkey/batch/500
time: [84.083 µs 84.690 µs 85.264 µs]
Benchmarking vrf_verify/single_proof
Benchmarking vrf_verify/single_proof: Warming up for 3.0000 s
Benchmarking vrf_verify/single_proof: Collecting 100 samples in estimated 5.1259 s (30k iterations)
Benchmarking vrf_verify/single_proof: Analyzing
vrf_verify/single_proof time: [168.46 µs 168.52 µs 168.59 µs]
Found 8 outliers among 100 measurements (8.00%)
5 (5.00%) high mild
3 (3.00%) high severe
Primitives
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking blake2b_256/hash/32B_txhash
Benchmarking blake2b_256/hash/32B_txhash: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/32B_txhash: Collecting 100 samples in estimated 5.0002 s (32M iterations)
Benchmarking blake2b_256/hash/32B_txhash: Analyzing
blake2b_256/hash/32B_txhash
time: [159.88 ns 160.72 ns 161.62 ns]
Found 16 outliers among 100 measurements (16.00%)
16 (16.00%) high mild
Benchmarking blake2b_256/hash/64B_vkey
Benchmarking blake2b_256/hash/64B_vkey: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/64B_vkey: Collecting 100 samples in estimated 5.0002 s (32M iterations)
Benchmarking blake2b_256/hash/64B_vkey: Analyzing
blake2b_256/hash/64B_vkey
time: [161.18 ns 162.54 ns 164.10 ns]
Benchmarking blake2b_256/hash/256B_small_tx
Benchmarking blake2b_256/hash/256B_small_tx: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/256B_small_tx: Collecting 100 samples in estimated 5.0014 s (18M iterations)
Benchmarking blake2b_256/hash/256B_small_tx: Analyzing
blake2b_256/hash/256B_small_tx
time: [278.98 ns 279.60 ns 280.18 ns]
Benchmarking blake2b_256/hash/500B_avg_tx
Benchmarking blake2b_256/hash/500B_avg_tx: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/500B_avg_tx: Collecting 100 samples in estimated 5.0010 s (9.3M iterations)
Benchmarking blake2b_256/hash/500B_avg_tx: Analyzing
blake2b_256/hash/500B_avg_tx
time: [537.89 ns 538.58 ns 539.26 ns]
Benchmarking blake2b_256/hash/1KB_tx_body
Benchmarking blake2b_256/hash/1KB_tx_body: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/1KB_tx_body: Collecting 100 samples in estimated 5.0017 s (4.8M iterations)
Benchmarking blake2b_256/hash/1KB_tx_body: Analyzing
blake2b_256/hash/1KB_tx_body
time: [1.0493 µs 1.0498 µs 1.0502 µs]
Benchmarking blake2b_256/hash/4KB_large_tx
Benchmarking blake2b_256/hash/4KB_large_tx: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/4KB_large_tx: Collecting 100 samples in estimated 5.0111 s (1.2M iterations)
Benchmarking blake2b_256/hash/4KB_large_tx: Analyzing
blake2b_256/hash/4KB_large_tx
time: [4.1175 µs 4.1183 µs 4.1191 µs]
Found 5 outliers among 100 measurements (5.00%)
3 (3.00%) high mild
2 (2.00%) high severe
Benchmarking blake2b_256/hash/16KB_block_header
Benchmarking blake2b_256/hash/16KB_block_header: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/16KB_block_header: Collecting 100 samples in estimated 5.0556 s (308k iterations)
Benchmarking blake2b_256/hash/16KB_block_header: Analyzing
blake2b_256/hash/16KB_block_header
time: [16.412 µs 16.415 µs 16.419 µs]
Found 6 outliers among 100 measurements (6.00%)
2 (2.00%) high mild
4 (4.00%) high severe
Benchmarking blake2b_256/hash/20KB_avg_block
Benchmarking blake2b_256/hash/20KB_avg_block: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/20KB_avg_block: Collecting 100 samples in estimated 5.0758 s (247k iterations)
Benchmarking blake2b_256/hash/20KB_avg_block: Analyzing
blake2b_256/hash/20KB_avg_block
time: [20.506 µs 20.509 µs 20.512 µs]
Found 7 outliers among 100 measurements (7.00%)
4 (4.00%) high mild
3 (3.00%) high severe
Benchmarking blake2b_256/hash/90KB_max_block
Benchmarking blake2b_256/hash/90KB_max_block: Warming up for 3.0000 s
Benchmarking blake2b_256/hash/90KB_max_block: Collecting 100 samples in estimated 5.1263 s (56k iterations)
Benchmarking blake2b_256/hash/90KB_max_block: Analyzing
blake2b_256/hash/90KB_max_block
time: [92.203 µs 92.215 µs 92.228 µs]
Found 5 outliers among 100 measurements (5.00%)
2 (2.00%) high mild
3 (3.00%) high severe
Benchmarking blake2b_224/hash/32B_vkey_to_keyhash
Benchmarking blake2b_224/hash/32B_vkey_to_keyhash: Warming up for 3.0000 s
Benchmarking blake2b_224/hash/32B_vkey_to_keyhash: Collecting 100 samples in estimated 5.0007 s (30M iterations)
Benchmarking blake2b_224/hash/32B_vkey_to_keyhash: Analyzing
blake2b_224/hash/32B_vkey_to_keyhash
time: [167.85 ns 168.54 ns 169.32 ns]
Found 16 outliers among 100 measurements (16.00%)
16 (16.00%) high severe
Benchmarking blake2b_224/hash/64B_script_bytes
Benchmarking blake2b_224/hash/64B_script_bytes: Warming up for 3.0000 s
Benchmarking blake2b_224/hash/64B_script_bytes: Collecting 100 samples in estimated 5.0005 s (30M iterations)
Benchmarking blake2b_224/hash/64B_script_bytes: Analyzing
blake2b_224/hash/64B_script_bytes
time: [169.28 ns 170.45 ns 171.86 ns]
Benchmarking blake2b_224/hash/256B_address_payload
Benchmarking blake2b_224/hash/256B_address_payload: Warming up for 3.0000 s
Benchmarking blake2b_224/hash/256B_address_payload: Collecting 100 samples in estimated 5.0013 s (17M iterations)
Benchmarking blake2b_224/hash/256B_address_payload: Analyzing
blake2b_224/hash/256B_address_payload
time: [285.16 ns 285.34 ns 285.51 ns]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
Benchmarking blake2b_batch/224_keyhashes/10
Benchmarking blake2b_batch/224_keyhashes/10: Warming up for 3.0000 s
Benchmarking blake2b_batch/224_keyhashes/10: Collecting 100 samples in estimated 5.0007 s (3.2M iterations)
Benchmarking blake2b_batch/224_keyhashes/10: Analyzing
blake2b_batch/224_keyhashes/10
time: [1.5672 µs 1.5900 µs 1.6162 µs]
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) high mild
3 (3.00%) high severe
Benchmarking blake2b_batch/224_keyhashes/50
Benchmarking blake2b_batch/224_keyhashes/50: Warming up for 3.0000 s
Benchmarking blake2b_batch/224_keyhashes/50: Collecting 100 samples in estimated 5.0335 s (641k iterations)
Benchmarking blake2b_batch/224_keyhashes/50: Analyzing
blake2b_batch/224_keyhashes/50
time: [7.8359 µs 7.9503 µs 8.0814 µs]
Found 4 outliers among 100 measurements (4.00%)
4 (4.00%) high severe
Benchmarking blake2b_batch/224_keyhashes/100
Benchmarking blake2b_batch/224_keyhashes/100: Warming up for 3.0000 s
Benchmarking blake2b_batch/224_keyhashes/100: Collecting 100 samples in estimated 5.0725 s (323k iterations)
Benchmarking blake2b_batch/224_keyhashes/100: Analyzing
blake2b_batch/224_keyhashes/100
time: [15.679 µs 15.908 µs 16.170 µs]
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high severe
Benchmarking blake2b_batch/224_keyhashes/500
Benchmarking blake2b_batch/224_keyhashes/500: Warming up for 3.0000 s
Benchmarking blake2b_batch/224_keyhashes/500: Collecting 100 samples in estimated 5.1563 s (66k iterations)
Benchmarking blake2b_batch/224_keyhashes/500: Analyzing
blake2b_batch/224_keyhashes/500
time: [78.451 µs 79.590 µs 80.894 µs]
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high severe
Benchmarking blake2b_batch/256_txbodies_500B/50
Benchmarking blake2b_batch/256_txbodies_500B/50: Warming up for 3.0000 s
Benchmarking blake2b_batch/256_txbodies_500B/50: Collecting 100 samples in estimated 5.1125 s (192k iterations)
Benchmarking blake2b_batch/256_txbodies_500B/50: Analyzing
blake2b_batch/256_txbodies_500B/50
time: [26.713 µs 26.756 µs 26.803 µs]
Benchmarking blake2b_batch/256_txbodies_500B/100
Benchmarking blake2b_batch/256_txbodies_500B/100: Warming up for 3.0000 s
Benchmarking blake2b_batch/256_txbodies_500B/100: Collecting 100 samples in estimated 5.1118 s (96k iterations)
Benchmarking blake2b_batch/256_txbodies_500B/100: Analyzing
blake2b_batch/256_txbodies_500B/100
time: [53.348 µs 53.421 µs 53.501 µs]
Benchmarking blake2b_batch/256_txbodies_500B/300
Benchmarking blake2b_batch/256_txbodies_500B/300: Warming up for 3.0000 s
Benchmarking blake2b_batch/256_txbodies_500B/300: Collecting 100 samples in estimated 5.6509 s (35k iterations)
Benchmarking blake2b_batch/256_txbodies_500B/300: Analyzing
blake2b_batch/256_txbodies_500B/300
time: [159.96 µs 160.07 µs 160.18 µs]
Serialization
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
Gnuplot not found, using plotters backend
Benchmarking serialization/encode_transaction/conway_2in_2out_2wit
Benchmarking serialization/encode_transaction/conway_2in_2out_2wit: Warming up for 3.0000 s
Benchmarking serialization/encode_transaction/conway_2in_2out_2wit: Collecting 100 samples in estimated 5.0020 s (2.4M iterations)
Benchmarking serialization/encode_transaction/conway_2in_2out_2wit: Analyzing
serialization/encode_transaction/conway_2in_2out_2wit
time: [2.1099 µs 2.1157 µs 2.1233 µs]
Found 6 outliers among 100 measurements (6.00%)
6 (6.00%) high severe
Benchmarking serialization/encode_transaction/body_only_2in_2out
Benchmarking serialization/encode_transaction/body_only_2in_2out: Warming up for 3.0000 s
Benchmarking serialization/encode_transaction/body_only_2in_2out: Collecting 100 samples in estimated 5.0035 s (3.7M iterations)
Benchmarking serialization/encode_transaction/body_only_2in_2out: Analyzing
serialization/encode_transaction/body_only_2in_2out
time: [1.3614 µs 1.3685 µs 1.3790 µs]
Found 14 outliers among 100 measurements (14.00%)
4 (4.00%) high mild
10 (10.00%) high severe
Benchmarking serialization/encode_block_header/with_vrf_output
Benchmarking serialization/encode_block_header/with_vrf_output: Warming up for 3.0000 s
Benchmarking serialization/encode_block_header/with_vrf_output: Collecting 100 samples in estimated 5.0008 s (3.9M iterations)
Benchmarking serialization/encode_block_header/with_vrf_output: Analyzing
serialization/encode_block_header/with_vrf_output
time: [1.2387 µs 1.2538 µs 1.2722 µs]
Found 12 outliers among 100 measurements (12.00%)
2 (2.00%) high mild
10 (10.00%) high severe
Benchmarking serialization/encode_value/ada_only
Benchmarking serialization/encode_value/ada_only: Warming up for 3.0000 s
Benchmarking serialization/encode_value/ada_only: Collecting 100 samples in estimated 5.0001 s (174M iterations)
Benchmarking serialization/encode_value/ada_only: Analyzing
serialization/encode_value/ada_only
time: [28.577 ns 28.645 ns 28.715 ns]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
Benchmarking serialization/encode_value/multi_asset_3policy_5asset
Benchmarking serialization/encode_value/multi_asset_3policy_5asset: Warming up for 3.0000 s
Benchmarking serialization/encode_value/multi_asset_3policy_5asset: Collecting 100 samples in estimated 5.0042 s (5.5M iterations)
Benchmarking serialization/encode_value/multi_asset_3policy_5asset: Analyzing
serialization/encode_value/multi_asset_3policy_5asset
time: [919.57 ns 920.92 ns 922.90 ns]
Found 7 outliers among 100 measurements (7.00%)
1 (1.00%) low mild
3 (3.00%) high mild
3 (3.00%) high severe
LSM stress tests
Raw measurements
info: syncing channel updates for 1.97.0-x86_64-unknown-linux-gnu
info: latest update on 2026-07-09 for version 1.97.0 (2d8144b78 2026-07-07)
info: downloading 3 components
running 3 tests
[test_mainnet_scale_delete_amplification] inserting 100000 entries...
[test_mainnet_scale_insert_read] inserting 100000 entries...
[test_mainnet_scale_wal_crash_recovery] writing 100000 entries (WAL only)...
[test_mainnet_scale_wal_crash_recovery] simulating crash (drop without flush)...
[test_mainnet_scale_insert_read] flush complete, sampling 1000 keys...
[test_mainnet_scale_insert_read] verified 1000/1000 sampled keys — PASS
test tree::mainnet_scale_tests::test_mainnet_scale_insert_read ... ok
[test_mainnet_scale_delete_amplification] insert flush complete
[test_mainnet_scale_delete_amplification] deleting 80000 entries...
[test_mainnet_scale_wal_crash_recovery] reopened, verifying 100000 entries...
[test_mainnet_scale_wal_crash_recovery] all 100000 entries recovered — PASS
test tree::mainnet_scale_tests::test_mainnet_scale_wal_crash_recovery ... ok
[test_mainnet_scale_delete_amplification] delete flush complete
[test_mainnet_scale_delete_amplification] verifying surviving 20000 entries...
[test_mainnet_scale_delete_amplification] verifying 1K deleted keys return None...
[test_mainnet_scale_delete_amplification] range scanning for exact count...
[test_mainnet_scale_delete_amplification] 20000 entries confirmed — PASS
test tree::mainnet_scale_tests::test_mainnet_scale_delete_amplification ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 100 filtered out; finished in 0.62s
Doc-tests dugite_lsm
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Third-Party Licenses
Dugite depends on a number of open-source Rust crates. This page documents all third-party dependencies and their license terms.
Total dependencies: 560
Generated from Cargo.lock on 2026-07-31 at commit 3cbe3986c8. Regenerate with just licenses after any dependency change — nothing in CI does it for you.
Dugite itself is licensed under Apache-2.0. Counts below are per unique crate name (the highest version, where a crate appears at several versions) across all target platforms, so they include target-gated dependencies such as the windows-* family that are not built on Linux or macOS.
License Summary
| License | Count |
|---|---|
| MIT OR Apache-2.0 | 285 |
| MIT | 119 |
| Apache-2.0 OR MIT | 44 |
| Apache-2.0 | 22 |
| Unicode-3.0 | 18 |
| Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | 14 |
| Unlicense OR MIT | 6 |
| Zlib OR Apache-2.0 OR MIT | 6 |
| BSD-3-Clause | 4 |
| MPL-2.0+ | 3 |
| Apache-2.0 OR ISC OR MIT | 3 |
| ISC | 3 |
| CC0-1.0 OR MIT-0 OR Apache-2.0 | 2 |
| MIT OR Apache-2.0 OR Zlib | 2 |
| BlueOak-1.0.0 | 2 |
| CDLA-Permissive-2.0 | 2 |
| BSD-2-Clause OR Apache-2.0 OR MIT | 2 |
| 0BSD OR MIT OR Apache-2.0 | 1 |
| Apache-2.0 WITH LLVM-exception | 1 |
| BSD-2-Clause | 1 |
| ISC AND (Apache-2.0 OR ISC) | 1 |
| ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND BSD-3-Clause AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0) | 1 |
| (Apache-2.0 OR MIT) AND BSD-3-Clause | 1 |
| MIT OR Apache-2.0 OR CC0-1.0 | 1 |
| MIT OR Apache-2.0 OR BSD-1-Clause | 1 |
| (MIT OR Apache-2.0) AND Unicode-DFS-2016 | 1 |
| Apache-2.0 OR MIT | 1 |
| Zlib | 1 |
| MIT AND BSD-3-Clause | 1 |
| MIT OR Zlib OR Apache-2.0 | 1 |
| (MIT OR Apache-2.0) AND Apache-2.0 | 1 |
| MIT OR Apache-2.0 OR LGPL-2.1-or-later | 1 |
| Apache-2.0 AND ISC | 1 |
| Apache-2.0 OR BSL-1.0 | 1 |
| MPL-2.0 OR MIT OR Apache-2.0 | 1 |
| MIT OR MPL-2.0 | 1 |
| WTFPL | 1 |
| (MIT OR Apache-2.0) AND Unicode-3.0 | 1 |
| Unknown | 1 |
| MIT AND Unicode-DFS-2016 | 1 |
Licenses Needing Review
Everything not covered by a plainly permissive license (MIT, Apache-2.0, BSD, ISC, Zlib, CC0, Unicode-3.0, BlueOak, CDLA-Permissive, or MPL-2.0 file-level copyleft). Review these before shipping a binary distribution:
| Crate | Version | License |
|---|---|---|
| bitmaps | 3.2.1 | MPL-2.0+ |
| imbl | 7.0.0 | MPL-2.0+ |
| imbl-sized-chunks | 0.1.3 | MPL-2.0+ |
| terminfo | 0.9.0 | WTFPL |
| vrf_dalek | 0.1.0 | Unknown |
| wezterm-bidi | 0.2.3 | MIT AND Unicode-DFS-2016 |
Key Dependencies
These are the primary libraries that Dugite directly depends on:
| Crate | Version | License | Description |
|---|---|---|---|
| tokio | 1.52.3 | MIT | An event-driven, non-blocking I/O platform for writing asynchronous I/O |
| backe... | |||
| tokio-util | 0.7.18 | MIT | Additional utilities for working with Tokio. |
| hyper | 1.10.1 | MIT | A protective and efficient HTTP library for all. |
| reqwest | 0.13.3 | MIT OR Apache-2.0 | higher level HTTP client library |
| socket2 | 0.6.3 | MIT OR Apache-2.0 | Utilities for handling networking sockets with a maximal amount of configurat... |
| hickory-resolver | 0.26.1 | MIT OR Apache-2.0 | hickory-resolver is a safe and secure DNS stub resolver library intended to b... |
| tonic | 0.14.6 | MIT | A gRPC over HTTP/2 implementation focused on high performance, interoperabili... |
| prost | 0.14.4 | Apache-2.0 | A Protocol Buffers implementation for the Rust Language. |
| serde | 1.0.228 | MIT OR Apache-2.0 | A generic serialization/deserialization framework |
| serde_json | 1.0.150 | MIT OR Apache-2.0 | A JSON serialization file format |
| minicbor | 0.26.5 | BlueOak-1.0.0 | A small CBOR codec suitable for no_std environments. |
| bincode | 2.0.1 | MIT | A binary serialization / deserialization strategy for transforming structs in... |
| toml | 1.1.2+spec-1.1.0 | MIT OR Apache-2.0 | A native Rust encoder and decoder of TOML-formatted files and streams. Provid... |
| blake2 | 0.9.2 | MIT OR Apache-2.0 | BLAKE2 hash functions |
| blake2b_simd | 1.0.4 | MIT | a pure Rust BLAKE2b implementation with dynamic SIMD |
| sha2 | 0.9.9 | MIT OR Apache-2.0 | Pure Rust implementation of the SHA-2 hash function family |
| including SHA-224,... | |||
| sha3 | 0.12.0 | MIT OR Apache-2.0 | Implementation of the SHA-3 family of cryptographic hash algorithms |
| ed25519-dalek | 2.2.0 | BSD-3-Clause | Fast and efficient ed25519 EdDSA key generations, signing, and verification i... |
| curve25519-dalek | 4.1.3 | BSD-3-Clause | A pure-Rust implementation of group operations on ristretto255 and Curve25519 |
| blst | 0.3.16 | Apache-2.0 | Bindings for blst BLS12-381 library |
| k256 | 0.13.4 | Apache-2.0 OR MIT | secp256k1 elliptic curve library written in pure Rust with support for ECDSA |
| ... | |||
| kes-summed-ed25519 | 0.2.1 | Apache-2.0 | Key Evolving Signature |
| vrf_dalek | 0.1.0 | Unknown | |
| num-bigint | 0.4.6 | MIT OR Apache-2.0 | Big integer implementation for Rust |
| num-rational | 0.4.2 | MIT OR Apache-2.0 | Rational numbers implementation for Rust |
| dashu-int | 0.4.2 | MIT OR Apache-2.0 | A big integer library with good performance |
| memmap2 | 0.9.11 | MIT OR Apache-2.0 | Cross-platform Rust API for memory-mapped file IO |
| fs2 | 0.4.3 | MIT/Apache-2.0 | Cross-platform file locks and file duplication. |
| imbl | 7.0.0 | MPL-2.0+ | Immutable collection datatypes |
| crc32fast | 1.5.0 | MIT OR Apache-2.0 | Fast, SIMD-accelerated CRC32 (IEEE) checksum computation |
| zstd | 0.13.3 | MIT | Binding for the zstd compression library. |
| tar | 0.4.46 | MIT OR Apache-2.0 | A Rust implementation of a TAR file reader and writer. This library does not |
| ... | |||
| hex | 0.4.3 | MIT OR Apache-2.0 | Encoding and decoding data into/from hexadecimal representation. |
| bs58 | 0.5.1 | MIT/Apache-2.0 | Another Base58 codec implementation. |
| bech32 | 0.11.1 | MIT | Encodes and decodes the Bech32 format and implements the bech32 and bech32m c... |
| base64 | 0.22.1 | MIT OR Apache-2.0 | encodes and decodes base64 as bytes or utf8 |
| mithril-client | 0.14.5 | Apache-2.0 | Mithril client library |
| clap | 4.6.1 | MIT OR Apache-2.0 | A simple to use, efficient, and full-featured Command Line Argument Parser |
| ratatui | 0.30.2 | MIT | A library that's all about cooking up terminal user interfaces |
| crossterm | 0.29.0 | MIT | A crossplatform terminal library for manipulating terminals. |
| indicatif | 0.18.6 | MIT | A progress bar and cli reporting library for Rust |
| tracing | 0.1.44 | MIT | Application-level tracing for Rust. |
| tracing-subscriber | 0.3.23 | MIT | Utilities for implementing and composing tracing subscribers. |
| dashmap | 6.2.1 | MIT | Blazing fast concurrent HashMap for Rust. |
| parking_lot | 0.12.5 | MIT OR Apache-2.0 | More compact and efficient implementations of the standard synchronization pr... |
| arc-swap | 1.9.2 | MIT OR Apache-2.0 | Atomically swappable Arc |
| rayon | 1.12.0 | MIT OR Apache-2.0 | Simple work-stealing parallelism for Rust |
| rand | 0.9.4 | MIT OR Apache-2.0 | Random number generators and other randomness functionality. |
| chrono | 0.4.44 | MIT OR Apache-2.0 | Date and time library for Rust |
All Dependencies
Complete list of all third-party crates used by Dugite, sorted alphabetically.
| Crate | Version | License |
|---|---|---|
| adler2 | 2.0.1 | 0BSD OR MIT OR Apache-2.0 |
| aho-corasick | 1.1.4 | Unlicense OR MIT |
| alloca | 0.4.0 | MIT |
| allocator-api2 | 0.2.21 | MIT OR Apache-2.0 |
| android_system_properties | 0.1.5 | MIT/Apache-2.0 |
| anes | 0.1.6 | MIT OR Apache-2.0 |
| anstream | 1.0.0 | MIT OR Apache-2.0 |
| anstyle | 1.0.14 | MIT OR Apache-2.0 |
| anstyle-parse | 1.0.0 | MIT OR Apache-2.0 |
| anstyle-query | 1.1.5 | MIT OR Apache-2.0 |
| anstyle-wincon | 3.0.11 | MIT OR Apache-2.0 |
| anyhow | 1.0.102 | MIT OR Apache-2.0 |
| approx | 0.5.1 | Apache-2.0 |
| ar_archive_writer | 0.5.1 | Apache-2.0 WITH LLVM-exception |
| arc-swap | 1.9.2 | MIT OR Apache-2.0 |
| archery | 1.2.2 | MIT |
| arraydeque | 0.5.1 | MIT/Apache-2.0 |
| arrayref | 0.3.9 | BSD-2-Clause |
| arrayvec | 0.7.6 | MIT OR Apache-2.0 |
| async-trait | 0.1.89 | MIT OR Apache-2.0 |
| atomic | 0.6.1 | Apache-2.0/MIT |
| atomic-waker | 1.1.2 | Apache-2.0 OR MIT |
| autocfg | 1.5.0 | Apache-2.0 OR MIT |
| aws-lc-rs | 1.17.0 | ISC AND (Apache-2.0 OR ISC) |
| aws-lc-sys | 0.41.0 | ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND BSD-3-Clause AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0) |
| axum | 0.8.9 | MIT |
| axum-core | 0.5.6 | MIT |
| az | 1.3.0 | MIT/Apache-2.0 |
| base16ct | 0.2.0 | Apache-2.0 OR MIT |
| base64 | 0.22.1 | MIT OR Apache-2.0 |
| base64ct | 1.8.3 | Apache-2.0 OR MIT |
| bech32 | 0.11.1 | MIT |
| bincode | 2.0.1 | MIT |
| bincode_derive | 2.0.1 | MIT |
| bindgen | 0.72.1 | BSD-3-Clause |
| bit-set | 0.8.0 | Apache-2.0 OR MIT |
| bit-vec | 0.8.0 | Apache-2.0 OR MIT |
| bitflags | 2.13.0 | MIT OR Apache-2.0 |
| bitmaps | 3.2.1 | MPL-2.0+ |
| blake2 | 0.9.2 | MIT OR Apache-2.0 |
| blake2b_simd | 1.0.4 | MIT |
| block-buffer | 0.9.0 | MIT OR Apache-2.0 |
| blst | 0.3.16 | Apache-2.0 |
| bs58 | 0.5.1 | MIT/Apache-2.0 |
| bumpalo | 3.20.2 | MIT OR Apache-2.0 |
| by_address | 1.2.1 | MIT OR Apache-2.0 |
| bytemuck | 1.25.0 | Zlib OR Apache-2.0 OR MIT |
| byteorder | 1.5.0 | Unlicense OR MIT |
| bytes | 1.12.0 | MIT |
| cast | 0.3.0 | MIT OR Apache-2.0 |
| castaway | 0.2.4 | MIT |
| cc | 1.2.60 | MIT OR Apache-2.0 |
| cexpr | 0.6.0 | Apache-2.0/MIT |
| cfg-if | 1.0.4 | MIT OR Apache-2.0 |
| cfg_aliases | 0.2.1 | MIT |
| chacha20 | 0.10.0 | MIT OR Apache-2.0 |
| chrono | 0.4.44 | MIT OR Apache-2.0 |
| ciborium | 0.2.2 | Apache-2.0 |
| ciborium-io | 0.2.2 | Apache-2.0 |
| ciborium-ll | 0.2.2 | Apache-2.0 |
| ckb-merkle-mountain-range | 0.6.1 | MIT |
| clang-sys | 1.8.1 | Apache-2.0 |
| clap | 4.6.1 | MIT OR Apache-2.0 |
| clap_builder | 4.6.0 | MIT OR Apache-2.0 |
| clap_derive | 4.6.1 | MIT OR Apache-2.0 |
| clap_lex | 1.1.0 | MIT OR Apache-2.0 |
| cmake | 0.1.58 | MIT OR Apache-2.0 |
| colorchoice | 1.0.5 | MIT OR Apache-2.0 |
| combine | 4.6.7 | MIT |
| compact_str | 0.9.0 | MIT |
| console | 0.16.4 | MIT |
| const-oid | 0.9.6 | Apache-2.0 OR MIT |
| constant_time_eq | 0.4.2 | CC0-1.0 OR MIT-0 OR Apache-2.0 |
| convert_case | 0.10.0 | MIT |
| core-foundation | 0.9.4 | MIT OR Apache-2.0 |
| core-foundation-sys | 0.8.7 | MIT OR Apache-2.0 |
| cpufeatures | 0.3.0 | MIT OR Apache-2.0 |
| crc32fast | 1.5.0 | MIT OR Apache-2.0 |
| criterion | 0.8.2 | Apache-2.0 OR MIT |
| criterion-plot | 0.8.2 | Apache-2.0 OR MIT |
| critical-section | 1.2.0 | MIT OR Apache-2.0 |
| crossbeam-channel | 0.5.15 | MIT OR Apache-2.0 |
| crossbeam-deque | 0.8.6 | MIT OR Apache-2.0 |
| crossbeam-epoch | 0.9.18 | MIT OR Apache-2.0 |
| crossbeam-utils | 0.8.21 | MIT OR Apache-2.0 |
| crossterm | 0.29.0 | MIT |
| crossterm_winapi | 0.9.1 | MIT |
| crunchy | 0.2.4 | MIT |
| crypto-bigint | 0.5.5 | Apache-2.0 OR MIT |
| crypto-common | 0.2.1 | MIT OR Apache-2.0 |
| crypto-mac | 0.8.0 | MIT OR Apache-2.0 |
| csscolorparser | 0.6.2 | MIT OR Apache-2.0 |
| curve25519-dalek | 4.1.3 | BSD-3-Clause |
| curve25519-dalek-derive | 0.1.1 | MIT/Apache-2.0 |
| darling | 0.23.0 | MIT |
| darling_core | 0.23.0 | MIT |
| darling_macro | 0.23.0 | MIT |
| dashmap | 6.2.1 | MIT |
| dashu-base | 0.4.3 | MIT OR Apache-2.0 |
| dashu-int | 0.4.2 | MIT OR Apache-2.0 |
| data-encoding | 2.11.0 | MIT |
| deltae | 0.3.2 | MIT |
| der | 0.7.10 | Apache-2.0 OR MIT |
| deranged | 0.5.8 | MIT OR Apache-2.0 |
| derive_more | 2.1.1 | MIT |
| derive_more-impl | 2.1.1 | MIT |
| digest | 0.9.0 | MIT OR Apache-2.0 |
| displaydoc | 0.2.5 | MIT OR Apache-2.0 |
| document-features | 0.2.12 | MIT OR Apache-2.0 |
| dunce | 1.0.5 | CC0-1.0 OR MIT-0 OR Apache-2.0 |
| dyn-clone | 1.0.20 | MIT OR Apache-2.0 |
| ecdsa | 0.16.9 | Apache-2.0 OR MIT |
| ed25519 | 2.2.3 | Apache-2.0 OR MIT |
| ed25519-dalek | 2.2.0 | BSD-3-Clause |
| either | 1.15.0 | MIT OR Apache-2.0 |
| elliptic-curve | 0.13.8 | Apache-2.0 OR MIT |
| encode_unicode | 1.0.0 | Apache-2.0 OR MIT |
| encoding_rs | 0.8.35 | (Apache-2.0 OR MIT) AND BSD-3-Clause |
| equivalent | 1.0.2 | Apache-2.0 OR MIT |
| erased-serde | 0.4.10 | MIT OR Apache-2.0 |
| errno | 0.3.14 | MIT OR Apache-2.0 |
| euclid | 0.22.14 | MIT OR Apache-2.0 |
| fancy-regex | 0.11.0 | MIT |
| fast-srgb8 | 1.0.0 | MIT OR Apache-2.0 OR CC0-1.0 |
| fastrand | 2.4.1 | Apache-2.0 OR MIT |
| ff | 0.13.1 | MIT/Apache-2.0 |
| fiat-crypto | 0.2.9 | MIT OR Apache-2.0 OR BSD-1-Clause |
| filedescriptor | 0.8.3 | MIT |
| filetime | 0.2.27 | MIT/Apache-2.0 |
| find-msvc-tools | 0.1.9 | MIT OR Apache-2.0 |
| finl_unicode | 1.4.0 | (MIT OR Apache-2.0) AND Unicode-DFS-2016 |
| fixed | 1.31.0 | MIT/Apache-2.0 |
| fixedbitset | 0.5.7 | MIT OR Apache-2.0 |
| flate2 | 1.1.9 | MIT OR Apache-2.0 |
| flume | 0.12.0 | Apache-2.0/MIT |
| fnv | 1.0.7 | Apache-2.0 / MIT |
| foldhash | 0.2.0 | Zlib |
| form_urlencoded | 1.2.2 | MIT OR Apache-2.0 |
| fs2 | 0.4.3 | MIT/Apache-2.0 |
| fs_extra | 1.3.0 | MIT |
| futures | 0.3.32 | MIT OR Apache-2.0 |
| futures-channel | 0.3.32 | MIT OR Apache-2.0 |
| futures-core | 0.3.32 | MIT OR Apache-2.0 |
| futures-executor | 0.3.32 | MIT OR Apache-2.0 |
| futures-io | 0.3.32 | MIT OR Apache-2.0 |
| futures-macro | 0.3.32 | MIT OR Apache-2.0 |
| futures-sink | 0.3.32 | MIT OR Apache-2.0 |
| futures-task | 0.3.32 | MIT OR Apache-2.0 |
| futures-util | 0.3.32 | MIT OR Apache-2.0 |
| generic-array | 0.14.9 | MIT |
| getrandom | 0.4.2 | MIT OR Apache-2.0 |
| glob | 0.3.3 | MIT OR Apache-2.0 |
| group | 0.13.0 | MIT/Apache-2.0 |
| h2 | 0.4.15 | MIT |
| half | 2.7.1 | MIT OR Apache-2.0 |
| hashbrown | 0.17.0 | MIT OR Apache-2.0 |
| hashlink | 0.10.0 | MIT OR Apache-2.0 |
| heck | 0.5.0 | MIT OR Apache-2.0 |
| hermit-abi | 0.5.2 | MIT OR Apache-2.0 |
| hex | 0.4.3 | MIT OR Apache-2.0 |
| hickory-net | 0.26.1 | MIT OR Apache-2.0 |
| hickory-proto | 0.26.1 | MIT OR Apache-2.0 |
| hickory-resolver | 0.26.1 | MIT OR Apache-2.0 |
| hmac | 0.12.1 | MIT OR Apache-2.0 |
| http | 1.4.0 | MIT OR Apache-2.0 |
| http-body | 1.0.1 | MIT |
| http-body-util | 0.1.3 | MIT |
| httparse | 1.10.1 | MIT OR Apache-2.0 |
| httpdate | 1.0.3 | MIT OR Apache-2.0 |
| hybrid-array | 0.4.10 | MIT OR Apache-2.0 |
| hyper | 1.10.1 | MIT |
| hyper-rustls | 0.27.8 | Apache-2.0 OR ISC OR MIT |
| hyper-timeout | 0.5.2 | MIT OR Apache-2.0 |
| hyper-util | 0.1.20 | MIT |
| iana-time-zone | 0.1.65 | MIT OR Apache-2.0 |
| iana-time-zone-haiku | 0.1.2 | MIT OR Apache-2.0 |
| icu_collections | 2.2.0 | Unicode-3.0 |
| icu_locale_core | 2.2.0 | Unicode-3.0 |
| icu_normalizer | 2.2.0 | Unicode-3.0 |
| icu_normalizer_data | 2.2.0 | Unicode-3.0 |
| icu_properties | 2.2.0 | Unicode-3.0 |
| icu_properties_data | 2.2.0 | Unicode-3.0 |
| icu_provider | 2.2.0 | Unicode-3.0 |
| id-arena | 2.3.0 | MIT/Apache-2.0 |
| ident_case | 1.0.1 | MIT/Apache-2.0 |
| idna | 1.1.0 | MIT OR Apache-2.0 |
| idna_adapter | 1.2.1 | Apache-2.0 OR MIT |
| imbl | 7.0.0 | MPL-2.0+ |
| imbl-sized-chunks | 0.1.3 | MPL-2.0+ |
| indexmap | 2.14.0 | Apache-2.0 OR MIT |
| indicatif | 0.18.6 | MIT |
| indoc | 2.0.7 | MIT OR Apache-2.0 |
| instability | 0.3.12 | MIT |
| inventory | 0.3.24 | MIT OR Apache-2.0 |
| ipconfig | 0.3.4 | MIT/Apache-2.0 |
| ipnet | 2.12.0 | MIT OR Apache-2.0 |
| iri-string | 0.7.12 | MIT OR Apache-2.0 |
| is_terminal_polyfill | 1.70.2 | MIT OR Apache-2.0 |
| itertools | 0.14.0 | MIT OR Apache-2.0 |
| itoa | 1.0.18 | MIT OR Apache-2.0 |
| jni | 0.22.4 | MIT OR Apache-2.0 |
| jni-macros | 0.22.4 | MIT OR Apache-2.0 |
| jni-sys | 0.4.1 | MIT OR Apache-2.0 |
| jni-sys-macros | 0.4.1 | MIT OR Apache-2.0 |
| jobserver | 0.1.34 | MIT OR Apache-2.0 |
| js-sys | 0.3.95 | MIT OR Apache-2.0 |
| k256 | 0.13.4 | Apache-2.0 OR MIT |
| kasuari | 0.4.12 | MIT OR Apache-2.0 |
| keccak | 0.2.0 | Apache-2.0 OR MIT |
| kes-summed-ed25519 | 0.2.1 | Apache-2.0 |
| lab | 0.11.0 | MIT |
| lazy_static | 1.5.0 | MIT OR Apache-2.0 |
| leb128fmt | 0.1.0 | MIT OR Apache-2.0 |
| libc | 0.2.186 | MIT OR Apache-2.0 |
| libloading | 0.8.9 | ISC |
| libm | 0.2.16 | MIT |
| libredox | 0.1.16 | MIT |
| line-clipping | 0.3.7 | MIT OR Apache-2.0 |
| linux-raw-sys | 0.12.1 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| litemap | 0.8.2 | Unicode-3.0 |
| litrs | 1.0.0 | MIT OR Apache-2.0 |
| lock_api | 0.4.14 | MIT OR Apache-2.0 |
| log | 0.4.29 | MIT OR Apache-2.0 |
| lru | 0.18.0 | MIT |
| lru-slab | 0.1.2 | MIT OR Apache-2.0 OR Zlib |
| mac_address | 1.1.8 | MIT OR Apache-2.0 |
| matchers | 0.2.0 | MIT |
| matchit | 0.8.4 | MIT AND BSD-3-Clause |
| memchr | 2.8.0 | Unlicense OR MIT |
| memmap2 | 0.9.11 | MIT OR Apache-2.0 |
| memmem | 0.1.1 | MIT/Apache-2.0 |
| memoffset | 0.9.1 | MIT |
| mime | 0.3.17 | MIT OR Apache-2.0 |
| minicbor | 0.26.5 | BlueOak-1.0.0 |
| minicbor-derive | 0.16.2 | BlueOak-1.0.0 |
| minimal-lexical | 0.2.1 | MIT/Apache-2.0 |
| miniz_oxide | 0.8.9 | MIT OR Zlib OR Apache-2.0 |
| mio | 1.2.0 | MIT |
| mithril-aggregator-client | 0.1.10 | Apache-2.0 |
| mithril-aggregator-discovery | 0.1.4 | Apache-2.0 |
| mithril-build-script | 0.2.28 | Apache-2.0 |
| mithril-cardano-node-internal-database | 0.1.11 | Apache-2.0 |
| mithril-client | 0.14.5 | Apache-2.0 |
| mithril-common | 0.6.67 | Apache-2.0 |
| mithril-stm | 0.10.5 | Apache-2.0 |
| moka | 0.12.15 | (MIT OR Apache-2.0) AND Apache-2.0 |
| multimap | 0.10.1 | MIT OR Apache-2.0 |
| ndk-context | 0.1.1 | MIT OR Apache-2.0 |
| netlink-packet-core | 0.7.0 | MIT |
| netlink-packet-sock-diag | 0.4.2 | MIT |
| netlink-packet-utils | 0.5.2 | MIT |
| netlink-sys | 0.8.8 | MIT |
| netstat2 | 0.11.2 | MIT OR Apache-2.0 |
| nix | 0.31.3 | MIT |
| nom | 8.0.0 | MIT |
| ntapi | 0.4.3 | Apache-2.0 OR MIT |
| nu-ansi-term | 0.50.3 | MIT |
| num-bigint | 0.4.6 | MIT OR Apache-2.0 |
| num-conv | 0.2.1 | MIT OR Apache-2.0 |
| num-derive | 0.4.2 | MIT OR Apache-2.0 |
| num-integer | 0.1.46 | MIT OR Apache-2.0 |
| num-modular | 0.6.1 | Apache-2.0 |
| num-order | 1.2.0 | Apache-2.0 |
| num-rational | 0.4.2 | MIT OR Apache-2.0 |
| num-traits | 0.2.19 | MIT OR Apache-2.0 |
| num_cpus | 1.17.0 | MIT OR Apache-2.0 |
| num_threads | 0.1.7 | MIT OR Apache-2.0 |
| objc2-core-foundation | 0.3.2 | Zlib OR Apache-2.0 OR MIT |
| objc2-io-kit | 0.3.2 | Zlib OR Apache-2.0 OR MIT |
| object | 0.37.3 | Apache-2.0 OR MIT |
| once_cell | 1.21.4 | MIT OR Apache-2.0 |
| once_cell_polyfill | 1.70.2 | MIT OR Apache-2.0 |
| oorandom | 11.1.5 | MIT |
| opaque-debug | 0.3.1 | MIT OR Apache-2.0 |
| openssl-probe | 0.2.1 | MIT OR Apache-2.0 |
| ordered-float | 5.3.0 | MIT |
| page_size | 0.6.0 | MIT/Apache-2.0 |
| palette | 0.7.6 | MIT OR Apache-2.0 |
| palette_derive | 0.7.6 | MIT OR Apache-2.0 |
| parking_lot | 0.12.5 | MIT OR Apache-2.0 |
| parking_lot_core | 0.9.12 | MIT OR Apache-2.0 |
| paste | 1.0.15 | MIT OR Apache-2.0 |
| percent-encoding | 2.3.2 | MIT OR Apache-2.0 |
| pest | 2.8.6 | MIT OR Apache-2.0 |
| pest_derive | 2.8.6 | MIT OR Apache-2.0 |
| pest_generator | 2.8.6 | MIT OR Apache-2.0 |
| pest_meta | 2.8.6 | MIT OR Apache-2.0 |
| petgraph | 0.8.3 | MIT OR Apache-2.0 |
| phf | 0.11.3 | MIT |
| phf_codegen | 0.11.3 | MIT |
| phf_generator | 0.11.3 | MIT |
| phf_macros | 0.11.3 | MIT |
| phf_shared | 0.11.3 | MIT |
| pin-project | 1.1.13 | Apache-2.0 OR MIT |
| pin-project-internal | 1.1.13 | Apache-2.0 OR MIT |
| pin-project-lite | 0.2.17 | Apache-2.0 OR MIT |
| pkcs8 | 0.10.2 | Apache-2.0 OR MIT |
| pkg-config | 0.3.33 | MIT OR Apache-2.0 |
| plain | 0.2.3 | MIT/Apache-2.0 |
| plotters | 0.3.7 | MIT |
| plotters-backend | 0.3.7 | MIT |
| plotters-svg | 0.3.7 | MIT |
| portable-atomic | 1.13.1 | Apache-2.0 OR MIT |
| potential_utf | 0.1.5 | Unicode-3.0 |
| powerfmt | 0.2.0 | MIT OR Apache-2.0 |
| ppv-lite86 | 0.2.21 | MIT OR Apache-2.0 |
| prefix-trie | 0.8.4 | MIT OR Apache-2.0 |
| prettyplease | 0.2.37 | MIT OR Apache-2.0 |
| proc-macro2 | 1.0.106 | MIT OR Apache-2.0 |
| proptest | 1.11.0 | MIT OR Apache-2.0 |
| prost | 0.14.4 | Apache-2.0 |
| prost-build | 0.14.4 | Apache-2.0 |
| prost-derive | 0.14.4 | Apache-2.0 |
| prost-types | 0.14.4 | Apache-2.0 |
| psm | 0.1.31 | MIT OR Apache-2.0 |
| pulldown-cmark | 0.13.4 | MIT |
| pulldown-cmark-to-cmark | 22.0.0 | Apache-2.0 |
| quick-error | 1.2.3 | MIT/Apache-2.0 |
| quinn | 0.11.9 | MIT OR Apache-2.0 |
| quinn-proto | 0.11.16 | MIT OR Apache-2.0 |
| quinn-udp | 0.5.14 | MIT OR Apache-2.0 |
| quote | 1.0.45 | MIT OR Apache-2.0 |
| r-efi | 6.0.0 | MIT OR Apache-2.0 OR LGPL-2.1-or-later |
| rand | 0.9.4 | MIT OR Apache-2.0 |
| rand_chacha | 0.9.0 | MIT OR Apache-2.0 |
| rand_core | 0.9.5 | MIT OR Apache-2.0 |
| rand_pcg | 0.10.2 | MIT OR Apache-2.0 |
| rand_xorshift | 0.4.0 | MIT OR Apache-2.0 |
| rand_xoshiro | 0.7.0 | MIT OR Apache-2.0 |
| ratatui | 0.30.2 | MIT |
| ratatui-core | 0.1.2 | MIT |
| ratatui-crossterm | 0.1.2 | MIT |
| ratatui-macros | 0.7.2 | MIT |
| ratatui-termina | 0.1.0 | MIT |
| ratatui-termwiz | 0.1.2 | MIT |
| ratatui-widgets | 0.3.2 | MIT |
| rayon | 1.12.0 | MIT OR Apache-2.0 |
| rayon-core | 1.13.0 | MIT OR Apache-2.0 |
| redox_syscall | 0.7.4 | MIT |
| ref-cast | 1.0.25 | MIT OR Apache-2.0 |
| ref-cast-impl | 1.0.25 | MIT OR Apache-2.0 |
| regex | 1.12.3 | MIT OR Apache-2.0 |
| regex-automata | 0.4.14 | MIT OR Apache-2.0 |
| regex-syntax | 0.8.10 | MIT OR Apache-2.0 |
| reqwest | 0.13.3 | MIT OR Apache-2.0 |
| resolv-conf | 0.7.6 | MIT OR Apache-2.0 |
| rfc6979 | 0.4.0 | Apache-2.0 OR MIT |
| ring | 0.17.14 | Apache-2.0 AND ISC |
| ripemd | 0.2.0 | MIT OR Apache-2.0 |
| rustc-hash | 2.1.2 | Apache-2.0 OR MIT |
| rustc_version | 0.4.1 | MIT OR Apache-2.0 |
| rustix | 1.1.4 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| rustls | 0.23.38 | Apache-2.0 OR ISC OR MIT |
| rustls-native-certs | 0.8.3 | Apache-2.0 OR ISC OR MIT |
| rustls-pki-types | 1.14.0 | MIT OR Apache-2.0 |
| rustls-platform-verifier | 0.7.0 | MIT OR Apache-2.0 |
| rustls-platform-verifier-android | 0.1.1 | MIT OR Apache-2.0 |
| rustls-webpki | 0.103.13 | ISC |
| rustversion | 1.0.22 | MIT OR Apache-2.0 |
| rusty-fork | 0.3.1 | MIT/Apache-2.0 |
| ryu | 1.0.23 | Apache-2.0 OR BSL-1.0 |
| safe_arch | 0.7.4 | Zlib OR Apache-2.0 OR MIT |
| same-file | 1.0.6 | Unlicense/MIT |
| saphyr | 0.0.6 | MIT OR Apache-2.0 |
| saphyr-parser | 0.0.6 | MIT OR Apache-2.0 |
| schannel | 0.1.29 | MIT |
| schemars | 1.2.1 | MIT |
| scopeguard | 1.2.0 | MIT OR Apache-2.0 |
| sec1 | 0.7.3 | Apache-2.0 OR MIT |
| security-framework | 3.7.0 | MIT OR Apache-2.0 |
| security-framework-sys | 2.17.0 | MIT OR Apache-2.0 |
| semver | 1.0.28 | MIT OR Apache-2.0 |
| serde | 1.0.228 | MIT OR Apache-2.0 |
| serde_bytes | 0.11.19 | MIT OR Apache-2.0 |
| serde_core | 1.0.228 | MIT OR Apache-2.0 |
| serde_derive | 1.0.228 | MIT OR Apache-2.0 |
| serde_json | 1.0.150 | MIT OR Apache-2.0 |
| serde_spanned | 1.1.1 | MIT OR Apache-2.0 |
| serde_urlencoded | 0.7.1 | MIT/Apache-2.0 |
| serde_with | 3.21.0 | MIT OR Apache-2.0 |
| serde_with_macros | 3.21.0 | MIT OR Apache-2.0 |
| sha2 | 0.9.9 | MIT OR Apache-2.0 |
| sha3 | 0.12.0 | MIT OR Apache-2.0 |
| sharded-slab | 0.1.7 | MIT |
| shlex | 1.3.0 | MIT OR Apache-2.0 |
| signal-hook | 0.3.18 | Apache-2.0/MIT |
| signal-hook-mio | 0.2.5 | MIT OR Apache-2.0 |
| signal-hook-registry | 1.4.8 | MIT OR Apache-2.0 |
| signature | 2.2.0 | Apache-2.0 OR MIT |
| simd-adler32 | 0.3.9 | MIT |
| simd_cesu8 | 1.1.1 | Apache-2.0 OR MIT |
| simdutf8 | 0.1.5 | MIT OR Apache-2.0 |
| siphasher | 1.0.2 | MIT/Apache-2.0 |
| slab | 0.4.12 | MIT |
| slog | 2.8.2 | MPL-2.0 OR MIT OR Apache-2.0 |
| smallvec | 1.15.1 | MIT OR Apache-2.0 |
| socket2 | 0.6.3 | MIT OR Apache-2.0 |
| spin | 0.9.8 | MIT |
| spki | 0.7.3 | Apache-2.0 OR MIT |
| sponge-cursor | 0.1.0 | MIT OR Apache-2.0 |
| stable_deref_trait | 1.2.1 | MIT OR Apache-2.0 |
| stacker | 0.1.24 | MIT OR Apache-2.0 |
| static_assertions | 1.1.0 | MIT OR Apache-2.0 |
| strsim | 0.11.1 | MIT |
| strum | 0.28.0 | MIT |
| strum_macros | 0.28.0 | MIT |
| subtle | 2.6.1 | BSD-3-Clause |
| symlink | 0.1.0 | MIT/Apache-2.0 |
| syn | 2.0.117 | MIT OR Apache-2.0 |
| sync_wrapper | 1.0.2 | Apache-2.0 |
| synstructure | 0.13.2 | MIT |
| sysinfo | 0.39.3 | MIT |
| system-configuration | 0.7.0 | MIT OR Apache-2.0 |
| system-configuration-sys | 0.6.0 | MIT OR Apache-2.0 |
| tagptr | 0.2.0 | MIT/Apache-2.0 |
| tar | 0.4.46 | MIT OR Apache-2.0 |
| tempfile | 3.27.0 | MIT OR Apache-2.0 |
| termina | 0.3.3 | MIT OR MPL-2.0 |
| terminfo | 0.9.0 | WTFPL |
| termios | 0.3.3 | MIT |
| termwiz | 0.23.3 | MIT |
| thiserror | 2.0.18 | MIT OR Apache-2.0 |
| thiserror-impl | 2.0.18 | MIT OR Apache-2.0 |
| thread_local | 1.1.9 | MIT OR Apache-2.0 |
| threadpool | 1.8.1 | MIT/Apache-2.0 |
| time | 0.3.47 | MIT OR Apache-2.0 |
| time-core | 0.1.8 | MIT OR Apache-2.0 |
| time-macros | 0.2.27 | MIT OR Apache-2.0 |
| tinystr | 0.8.3 | Unicode-3.0 |
| tinytemplate | 1.2.1 | Apache-2.0 OR MIT |
| tinyvec | 1.11.0 | Zlib OR Apache-2.0 OR MIT |
| tinyvec_macros | 0.1.1 | MIT OR Apache-2.0 OR Zlib |
| tokio | 1.52.3 | MIT |
| tokio-macros | 2.7.0 | MIT |
| tokio-rustls | 0.26.4 | MIT OR Apache-2.0 |
| tokio-stream | 0.1.18 | MIT |
| tokio-util | 0.7.18 | MIT |
| toml | 1.1.2+spec-1.1.0 | MIT OR Apache-2.0 |
| toml_datetime | 1.1.1+spec-1.1.0 | MIT OR Apache-2.0 |
| toml_parser | 1.1.2+spec-1.1.0 | MIT OR Apache-2.0 |
| toml_writer | 1.1.1+spec-1.1.0 | MIT OR Apache-2.0 |
| tonic | 0.14.6 | MIT |
| tonic-build | 0.14.6 | MIT |
| tonic-prost | 0.14.6 | MIT |
| tonic-prost-build | 0.14.6 | MIT |
| tonic-reflection | 0.14.6 | MIT |
| tonic-web | 0.14.6 | MIT |
| tower | 0.5.3 | MIT |
| tower-http | 0.6.8 | MIT |
| tower-layer | 0.3.3 | MIT |
| tower-service | 0.3.3 | MIT |
| tracing | 0.1.44 | MIT |
| tracing-appender | 0.2.5 | MIT |
| tracing-attributes | 0.1.31 | MIT |
| tracing-core | 0.1.36 | MIT |
| tracing-log | 0.2.0 | MIT |
| tracing-serde | 0.2.0 | MIT |
| tracing-subscriber | 0.3.23 | MIT |
| try-lock | 0.2.5 | MIT |
| typeid | 1.0.3 | MIT OR Apache-2.0 |
| typenum | 1.19.0 | MIT OR Apache-2.0 |
| typetag | 0.2.21 | MIT OR Apache-2.0 |
| typetag-impl | 0.2.21 | MIT OR Apache-2.0 |
| ucd-trie | 0.1.7 | MIT OR Apache-2.0 |
| unarray | 0.1.4 | MIT OR Apache-2.0 |
| unicase | 2.9.0 | MIT OR Apache-2.0 |
| unicode-ident | 1.0.24 | (MIT OR Apache-2.0) AND Unicode-3.0 |
| unicode-segmentation | 1.13.2 | MIT OR Apache-2.0 |
| unicode-truncate | 2.0.1 | MIT OR Apache-2.0 |
| unicode-width | 0.2.2 | MIT OR Apache-2.0 |
| unicode-xid | 0.2.6 | MIT OR Apache-2.0 |
| unit-prefix | 0.5.2 | MIT |
| untrusted | 0.9.0 | ISC |
| unty | 0.0.4 | MIT OR Apache-2.0 |
| url | 2.5.8 | MIT OR Apache-2.0 |
| utf8_iter | 1.0.4 | Apache-2.0 OR MIT |
| utf8parse | 0.2.2 | Apache-2.0 OR MIT |
| uuid | 1.23.1 | Apache-2.0 OR MIT |
| valuable | 0.1.1 | MIT |
| version_check | 0.9.5 | MIT/Apache-2.0 |
| virtue | 0.0.18 | MIT |
| vrf_dalek | 0.1.0 | Unknown |
| vtparse | 0.6.2 | MIT |
| wait-timeout | 0.2.1 | MIT/Apache-2.0 |
| walkdir | 2.5.0 | Unlicense/MIT |
| want | 0.3.1 | MIT |
| wasi | 0.9.0+wasi-snapshot-preview1 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wasip2 | 1.0.2+wasi-0.2.9 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wasip3 | 0.4.0+wasi-0.3.0-rc-2026-01-06 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wasm-bindgen | 0.2.118 | MIT OR Apache-2.0 |
| wasm-bindgen-futures | 0.4.68 | MIT OR Apache-2.0 |
| wasm-bindgen-macro | 0.2.118 | MIT OR Apache-2.0 |
| wasm-bindgen-macro-support | 0.2.118 | MIT OR Apache-2.0 |
| wasm-bindgen-shared | 0.2.118 | MIT OR Apache-2.0 |
| wasm-encoder | 0.244.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wasm-metadata | 0.244.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wasm-streams | 0.5.0 | MIT OR Apache-2.0 |
| wasmparser | 0.244.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| web-sys | 0.3.95 | MIT OR Apache-2.0 |
| web-time | 1.1.0 | MIT OR Apache-2.0 |
| webpki-root-certs | 1.0.7 | CDLA-Permissive-2.0 |
| webpki-roots | 1.0.6 | CDLA-Permissive-2.0 |
| wezterm-bidi | 0.2.3 | MIT AND Unicode-DFS-2016 |
| wezterm-blob-leases | 0.1.1 | MIT |
| wezterm-color-types | 0.3.0 | MIT |
| wezterm-dynamic | 0.2.1 | MIT |
| wezterm-dynamic-derive | 0.1.1 | MIT |
| wezterm-input-types | 0.1.0 | MIT |
| wide | 0.7.33 | Zlib OR Apache-2.0 OR MIT |
| widestring | 1.2.1 | MIT OR Apache-2.0 |
| winapi | 0.3.9 | MIT/Apache-2.0 |
| winapi-i686-pc-windows-gnu | 0.4.0 | MIT/Apache-2.0 |
| winapi-util | 0.1.11 | Unlicense OR MIT |
| winapi-x86_64-pc-windows-gnu | 0.4.0 | MIT/Apache-2.0 |
| windows | 0.62.2 | MIT OR Apache-2.0 |
| windows-collections | 0.3.2 | MIT OR Apache-2.0 |
| windows-core | 0.62.2 | MIT OR Apache-2.0 |
| windows-future | 0.3.2 | MIT OR Apache-2.0 |
| windows-implement | 0.60.2 | MIT OR Apache-2.0 |
| windows-interface | 0.59.3 | MIT OR Apache-2.0 |
| windows-link | 0.2.1 | MIT OR Apache-2.0 |
| windows-numerics | 0.3.1 | MIT OR Apache-2.0 |
| windows-registry | 0.6.1 | MIT OR Apache-2.0 |
| windows-result | 0.4.1 | MIT OR Apache-2.0 |
| windows-strings | 0.5.1 | MIT OR Apache-2.0 |
| windows-sys | 0.61.2 | MIT OR Apache-2.0 |
| windows-targets | 0.52.6 | MIT OR Apache-2.0 |
| windows-threading | 0.2.1 | MIT OR Apache-2.0 |
| windows_aarch64_gnullvm | 0.52.6 | MIT OR Apache-2.0 |
| windows_aarch64_msvc | 0.52.6 | MIT OR Apache-2.0 |
| windows_i686_gnu | 0.52.6 | MIT OR Apache-2.0 |
| windows_i686_gnullvm | 0.52.6 | MIT OR Apache-2.0 |
| windows_i686_msvc | 0.52.6 | MIT OR Apache-2.0 |
| windows_x86_64_gnu | 0.52.6 | MIT OR Apache-2.0 |
| windows_x86_64_gnullvm | 0.52.6 | MIT OR Apache-2.0 |
| windows_x86_64_msvc | 0.52.6 | MIT OR Apache-2.0 |
| winnow | 1.0.1 | MIT |
| wit-bindgen | 0.51.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wit-bindgen-core | 0.51.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wit-bindgen-rust | 0.51.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wit-bindgen-rust-macro | 0.51.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wit-component | 0.244.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| wit-parser | 0.244.0 | Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT |
| writeable | 0.6.3 | Unicode-3.0 |
| xattr | 1.6.1 | MIT OR Apache-2.0 |
| yoke | 0.8.2 | Unicode-3.0 |
| yoke-derive | 0.8.2 | Unicode-3.0 |
| zerocopy | 0.8.48 | BSD-2-Clause OR Apache-2.0 OR MIT |
| zerocopy-derive | 0.8.48 | BSD-2-Clause OR Apache-2.0 OR MIT |
| zerofrom | 0.1.7 | Unicode-3.0 |
| zerofrom-derive | 0.1.7 | Unicode-3.0 |
| zeroize | 1.9.0 | Apache-2.0 OR MIT |
| zeroize_derive | 1.5.0 | Apache-2.0 OR MIT |
| zerotrie | 0.2.4 | Unicode-3.0 |
| zerovec | 0.11.6 | Unicode-3.0 |
| zerovec-derive | 0.11.3 | Unicode-3.0 |
| zmij | 1.0.21 | MIT |
| zstd | 0.13.3 | MIT |
| zstd-safe | 7.2.4 | MIT OR Apache-2.0 |
| zstd-sys | 2.0.16+zstd.1.5.7 | MIT/Apache-2.0 |
Regenerating This Page
This page is generated from Cargo.lock metadata. To regenerate after dependency changes:
just licenses
# equivalently:
python3 scripts/dev/generate-licenses.py > docs/src/reference/third-party-licenses.md
Native (non-Rust) code reaches the build through a handful of crates worth naming explicitly:
| Component | Arrives via | Notes |
|---|---|---|
BLS12-381 (blst, C) | direct dependency of dugite-uplc | Plutus BLS builtins; also pulled transitively by mithril-stm |
| zstd (C) | zstd / zstd-sys | Mithril snapshot decompression |
| ring / aws-lc (C, asm) | rustls stack under reqwest, tonic, mithril-client | TLS |
| libsecp256k1 (C) | not used | dugite-uplc deliberately uses pure-Rust k256 instead of secp256k1-sys |
| libsodium | not used | VRF is pure Rust via a forked curve25519-dalek |
Four dependencies are git-pinned rather than published to crates.io:
vrf_dalek and curve25519-dalek-fork (the IETF-03 VRF-compatible fork,
required for Praos leader election), plus amaru-uplc and cddl, which are
both development/conformance-only.
Troubleshooting
Common issues and their solutions when running Dugite.
Build Issues
Compilation is slow
The initial build compiles all dependencies from source, which takes several minutes. Subsequent builds are much faster due to cargo caching.
For faster development iteration, use debug builds:
cargo build # debug mode, faster compilation
Only use --release when running against a live network.
Connection Issues
Cannot connect to peers
Symptoms: Node starts but never receives blocks. Logs show connection failures.
Possible causes:
-
Firewall blocking outbound connections on port 3001. Ensure outbound TCP connections to port 3001 are allowed.
-
Incorrect network magic. Verify the
NetworkMagicin your config matches the target network:- Mainnet:
764824073 - Preview:
2 - Preprod:
1
- Mainnet:
-
DNS resolution failure. If topology uses hostnames, ensure DNS is working:
nslookup preview-node.play.dev.cardano.org -
Stale topology. Peer addresses may change. Download the latest topology from the Cardano Operations Book.
Handshake failures
Error: Handshake failed: version mismatch
Dugite proposes N2N versions 15 and 14 (preferring 15) and N2C versions 16 through 23. This error usually means the peer supports neither N2N 14 nor 15. Note that network-magic mismatch produces a refusal, not a version mismatch — check both. Ensure you are connecting to an up-to-date cardano-node:
- Mainnet / Preprod: cardano-node 10.x+ required
- Preview: cardano-node 11.0.1+ required (preview is at Protocol Version 11; peers running 10.x will reject the handshake)
Socket Issues
Cannot connect to node socket
Error: Cannot connect to node socket './node.sock': No such file or directory
Solutions:
-
Node is not running. Start the node first.
-
Wrong socket path. Verify the socket path matches what the node was started with:
dugite-cli query tip --socket-path /path/to/actual/node.sock -
Permission denied. Ensure the user running the CLI has read/write access to the socket file.
-
Stale socket file. If the node crashed, the socket file may remain. Delete it and restart:
rm ./node.sock dugite-node run ...
Socket permission denied
Error: Permission denied (os error 13)
The Unix socket file inherits the permissions of the process that created it. Ensure both the node and CLI processes run as the same user, or adjust the socket file permissions.
Stopping the Node
Always stop the node with SIGTERM (or SIGINT / Ctrl-C). Never kill -9.
On SIGTERM the node demotes its peers, flushes volatile blocks to the
ImmutableDB, fsyncs the chunk and secondary index files, writes the primary
index and tip.meta, persists the mmap block index, stamps the
immutable/clean marker, and finally saves a ledger snapshot. The flush and
persist phase has a 30 s budget; the final snapshot has its own 120 s budget on
large databases. Wait for the process to actually exit.
kill -9 skips every one of those steps. The recorded consequences are a
rebuilt block index at minimum, and — historically — a lost active-chunk index
that cost roughly ten hours of blocks.
A second SIGINT/SIGTERM during shutdown forces an immediate exit
(exit 130 / exit 143), so do not repeat the signal while waiting.
kill $(pidof dugite-node) # correct
systemctl stop dugite-node # correct
kill -9 $(pidof dugite-node) # do not do this
Storage Issues
Database directory is locked
Error:
database directory is locked by another dugite process (pid 12345) — refusing
concurrent open of /path/to/db/lock (issue #929: a second writer would corrupt
the ImmutableDB)
Since v2.4.0 dugite-node run takes an exclusive advisory flock on
<database-path>/lock before touching any other file, and holds it for the
process lifetime. Two writers on one directory corrupt the ImmutableDB, so the
second one fails fast and names the PID holding the lock.
What to do:
- Check whether that PID is still alive (
ps -p 12345). If your previous node is still shutting down, wait — the lock is released when its file descriptor closes. - If two nodes are genuinely configured against the same
--database-path, give each its own directory. Ports are not the only thing that must be distinct. - A crashed process leaves no stale lock — the kernel releases the flock on
process death. The
lockfile itself is never deleted; its presence alone means nothing.
Two caveats:
dugite-node db infoalso opens the ChainDB read-write, so it takes the same lock and will fail against a live node. This is by design.mithril-importdoes not take the lock. It will happily delete the immutable directory out from under a running node. Stop the node first.
Unclean shutdown detected
Log:
ImmutableDB: unclean shutdown detected (no clean marker) — rebuilding mmap block
index from secondary entries (#928)
The <db>/immutable/clean marker is written by the graceful shutdown flush and
removed the moment the node opens the database for writing. Its absence at
startup means the previous stop was not graceful, so the persistent mmap hash
index cannot be trusted and is rebuilt from the secondary index entries.
This is recovery working, not a fault. It costs startup time proportional to
database size, and nothing else. If you see it after every restart, your stop
procedure is sending SIGKILL somewhere — check your service manager's
KillSignal and TimeoutStopSec.
Chunk reconciliation and quarantine on open
Every open reconciles the on-disk chunks before any index is trusted. The messages you may see, and what each means:
| Log line | Meaning |
|---|---|
truncating torn trailing bytes from tail chunk's secondary index | Partial index write from a hard stop; trimmed |
reconciling tail chunk — truncating to the verified prefix; dropped blocks will be re-fetched from peers | The tail chunk was CRC-verified block by block and cut back to the last good one. The dropped blocks come back from peers |
quarantining unservable tail chunk — data preserved, blocks will be re-fetched from peers | The tail chunk could not be verified at all. Renamed to <NNNNN>.chunk.orphaned; its .secondary and .primary are deleted |
tail chunk does not chain onto the previous chunk — quarantining the orphan island above the hole | The chain has a break at the tail boundary; everything above it is quarantined |
Quarantined data is preserved, never deleted. Once the node is healthy you
can remove the .chunk.orphaned files.
Inconsistent chunk — node refuses to start
Error:
inconsistent chunk 00123 in ImmutableDB: <reason>. Refusing to open with a hole
below the tip (issues #926/#928) — restore the damaged chunk (e.g.
`dugite-node mithril-import`) or remove the damaged chunk files manually
This is deliberate. Damage at the tail is recoverable by truncation or quarantine; damage below the tail would leave a hole in the middle of the chain, and serving from a holed chain is worse than refusing to start.
Recovery, in order of preference:
# 1. Re-import from Mithril into a fresh directory (fastest)
dugite-node mithril-import --network-magic <magic> --database-path ./db-new
# 2. Full resync from genesis (slowest, always works)
rm -rf ./db-path
dugite-node run ...
Ledger tip is below the ImmutableDB tip
Log (WARN, at startup after replay):
Ledger tip is BELOW the ImmutableDB tip after replay — the immutable chain
contains blocks the ledger could not apply ... Sync will advance the ledger from
ChainDB via the gap-bridge where possible; if this gap persists, inspect the
seam and consider re-import via `dugite-node mithril-import` (#927).
The immutable chain holds blocks the ledger could not apply — typically after crash damage or a replay apply failure. The node handles this rather than wedging: it offers its known points newest-first by slot (immutable tip ahead of the stale ledger tip), and it exempts the peer's initial protocol- mandated rollback to the exactly-agreed intersection from the divergent-peer guard. Before this behaviour existed, the guard disconnected every peer for rolling back to a point the node had itself offered, and sync stopped forever.
What to do: watch the gap. If ledger_slot climbs toward
immutable_tip_slot over the next few minutes, it is self-healing — leave it.
If it stays flat, re-import.
Ledger snapshot rejected on startup
Log:
Quarantined unreadable ledger snapshot — chain will be replayed from ImmutableDB
on next start. Inspect or delete the .v{NN}-unreadable file once recovery
completes.
The snapshot's SNAPSHOT_VERSION does not match this build's. Expected after an
upgrade that bumps it — see Upgrading. The file is renamed to
<name>.bin.v<NN>-unreadable, never deleted, and the ledger is rebuilt by
replaying ImmutableDB chunks. Blocks are not lost.
Related messages that do not quarantine (delete the snapshot by hand if they
recur): Snapshot is missing the DUGT framing header, Snapshot checksum mismatch — file may be corrupted.
Database corruption (last resort)
If none of the targeted recoveries above apply, delete the database and resync:
rm -rf ./db-path
dugite-node run ...
For faster recovery, use Mithril snapshot import:
rm -rf ./db-path
dugite-node mithril-import --network-magic 2 --database-path ./db-path
dugite-node run ...
Disk space
Cardano databases grow continuously. Approximate sizes:
| Network | Database Size |
|---|---|
| Mainnet | 90-140+ GB |
| Preview | 8-15+ GB |
| Preprod | 20-35+ GB |
Monitor disk usage and ensure adequate free space.
Sync Issues
Sync is slow
Possible causes:
-
Single peer. Dugite benefits from multiple peers for block fetching. Ensure your topology includes multiple bootstrap peers or enable ledger-based peer discovery.
-
Network latency. The ChainSync protocol has an inherent per-header RTT (~300ms). High-latency connections will reduce throughput.
-
Slow disk. Storage performance depends on disk I/O speed. SSDs are strongly recommended. On Linux, enable
io_uringfor improved UTxO storage performance:cargo build --release --features io-uring. -
CPU-bound during ledger validation. Block processing includes UTxO validation and Plutus script execution. This is CPU-intensive during sync.
Recommendation: Use Mithril snapshot import to bypass the initial sync bottleneck entirely.
Sync stalls
Symptoms: Progress percentage stops increasing, no new blocks logged.
Possible causes:
-
Peer disconnected. The node will reconnect automatically with exponential backoff. Wait a few minutes.
-
All peers at same height. If all configured peers are also syncing, they may not have new blocks to serve. Add more peers to the topology.
-
Resource exhaustion. Check for out-of-memory or file descriptor limits.
Memory Issues
Out of memory
Dugite's memory usage depends on:
- UTxO set size (the largest memory consumer)
- Number of connected peers
- VolatileDB (last k=2160 blocks in memory)
For mainnet, expect memory usage of 8-16 GB depending on sync progress.
If running on a memory-constrained system, ensure adequate swap space is configured.
Logging
Increase log verbosity
Use the RUST_LOG environment variable:
# Debug all crates
RUST_LOG=debug dugite-node run ...
# Debug specific crate
RUST_LOG=dugite_network=debug dugite-node run ...
# Trace level (very verbose)
RUST_LOG=trace dugite-node run ...
Log to file
Use the built-in file logging:
dugite-node run --log-output file --log-dir /var/log/dugite ...
Log files are rotated daily by default. See Logging for rotation options and multi-target output.
SIGHUP: Topology Reload and Log Verbosity
Sending SIGHUP to the node reloads the topology file and the hot-reloadable
parts of the node config, without a restart. Fields that require a restart are
named in the log and ignored (config_reload: restart-required fields changed — ignored), so a SIGHUP never half-applies a change.
-
Topology reload — The node re-reads the topology file and updates the peer manager:
# Edit topology.json, then: kill -HUP $(pidof dugite-node) -
Log verbosity reload — If
LogDirectiveis set in the config file, the per-subsystem log filter is reloaded:# Add/update in your config JSON: # "LogDirective": "info,dugite_network=trace" # # Then send SIGHUP: kill -HUP $(pidof dugite-node)This is useful for enabling trace logging for a specific subsystem while the node is running, without disrupting sync or block production.
See Logging for full details.
Block Producer Issues
Block producer shows ZERO stake
Cause: Snapshot loaded before UTxO store was attached, corrupting pool_stake values.
Fix: Automatic on restart — rebuild_stake_distribution runs after UTxO store attachment.
Verify: Check the log for "Block producer: pool stake in 'set' snapshot" with a non-zero pool_stake_lovelace value.
Node enters reconnection loop after forging
Cause: Forged block lost a slot battle and was persisted to ImmutableDB.
Symptoms: Log shows "intersection fell to Origin" or the node repeatedly reconnects to upstream peers.
Fix: The fork recovery mechanism now handles this automatically. If the issue persists, re-import from Mithril:
dugite-node mithril-import --network-magic <magic> --database-path <path>
See Fork Recovery & ImmutableDB Contamination for details on how the recovery mechanism works.
Epoch & State Issues
Epoch number appears wrong (e.g., epoch 445 instead of 1239)
Cause: Snapshot saved with incorrect epoch_length defaults (mainnet 432000 instead of preview 86400).
Fix: Automatic correction on load — the epoch is recalculated from the tip slot using genesis parameters.
Log message: "Snapshot epoch differs from computed epoch — correcting"
VRF verification failures after restart
Cause: Epoch nonce in snapshot may be stale if saved with wrong epoch boundaries, or the node is replaying blocks in non-strict mode.
Fix: VRF verification is non-fatal during non-strict (initial sync / replay) mode. Once the node reaches the chain tip it enables strict verification and the serialized epoch_nonce from the snapshot is used directly — matching Haskell's behavior.
Getting Help
If you encounter an issue not covered here:
- Check the GitHub issues
- Open a new issue with:
- Dugite version (
dugite-node --version) - Operating system
- Configuration files (redact any sensitive info)
- Relevant log output
- Steps to reproduce
- Dugite version (