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: InitiatorOnly or InitiatorAndResponder
  • 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 point
  • MsgRequestNext — Request the next header
  • MsgRollForward — Header delivered
  • MsgRollBackward — Chain reorganization
  • MsgAwaitReply — 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 range
  • MsgBlock — Block delivered
  • MsgNoBlocks — Requested blocks not available
  • MsgBatchDone — 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:

  1. MsgAcquire — Lock the ledger state at the current tip
  2. MsgQuery — Execute queries against the locked state
  3. MsgRelease — 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

TagQueryDescription
0GetLedgerTipCurrent slot, hash, and block number
1GetEpochNoActive epoch number
2GetCurrentPParamsLive protocol parameters (positional array(31) CBOR encoding matching Haskell ConwayPParams EncCBOR)
3GetProposedPParamsUpdatesProposed parameter updates (empty map in Conway)
4GetStakeDistributionPool stake distribution with pledge
5GetNonMyopicMemberRewardsEstimated rewards per pool for given stake amounts
6GetUTxOByAddressUTxO set filtered by address (Cardano wire format Map<[tx_hash, index], {0: addr, 1: value, 2: datum}>)
7GetUTxOWholeEntire UTxO set (expensive; used by testing tools)
8DebugEpochStateSimplified epoch state summary (treasury, reserves, active stake totals)
9GetCBORMeta-query that wraps the result of an inner query in CBOR tag(24), returning raw bytes
10GetFilteredDelegationsAndRewardAccountsDelegation targets and reward balances for a set of stake credentials
11GetGenesisConfigSystem start, epoch length, slot length, and security parameter
12DebugNewEpochStateSimplified new epoch state summary (epoch number, block count, snapshot state)
13DebugChainDepStateChain-dependent state summary (last applied block, operational certificate counters)
14GetRewardProvenanceReward calculation provenance: reward pot, treasury tax rate, total active stake, per-pool reward breakdown
15GetUTxOByTxInUTxO set filtered by transaction inputs
16GetStakePoolsSet of all registered pool key hashes
17GetStakePoolParamsRegistered pool parameters (owner, cost, margin, pledge, relays, metadata)
18GetRewardInfoPoolsPer-pool reward breakdown: relative stake, leader and member reward splits, pool margin, fixed cost, and performance metrics
19GetPoolStateQueryPoolStateResult encoded as array(4): [poolParams, futurePoolParams, retiring, deposits]
20GetStakeSnapshotsMark/set/go stake snapshots used for leader schedule calculation
21GetPoolDistrPool stake distribution with VRF verification key hashes
22GetStakeDelegDepositsDeposit amounts per registered stake credential
23GetConstitutionConstitution anchor (URL + hash) and optional guardrail script hash
24GetGovStateConwayGovState encoded as array(7) CBOR: active proposals, committee state, constitution, current/previous protocol parameters, future parameters, and DRep pulse state
25GetDRepStateRegistered DReps with their delegation counts and deposit balances (supports credential filter)
26GetDRepStakeDistrTotal delegated stake per DRep (lovelace)
27GetCommitteeMembersStateConstitutional committee members, iterating committee_expiration entries with hot_credential_type for each member
28GetFilteredVoteDelegateesVote delegation map per stake credential
29GetAccountStateTreasury and reserves balances
30GetSPOStakeDistrPer-pool stake distribution filtered by a set of pool IDs
31GetProposalsActive governance proposals with optional governance action ID filter
32GetRatifyStateEnacted and expired proposals along with the ratify_delayed flag
33GetFuturePParamsPending protocol parameter changes scheduled for the next epoch (if any)
34GetLedgerPeerSnapshotSPO relay addresses weighted by relative stake, used for P2P ledger-based peer discovery
35QueryStakePoolDefaultVoteDefault vote per pool derived from its DRep delegation (AlwaysAbstain, AlwaysNoConfidence, or specific DRep vote)
36GetPoolDistr2Extended pool distribution including total_active_stake alongside per-pool entries
37GetStakeDistribution2Extended stake distribution including total_active_stake
38GetMaxMajorProtocolVersionMaximum supported major protocol version (returns 10)

Cross-Era Queries

In addition to the Shelley BlockQuery tags, the following queries operate outside the HFC era-specific envelope:

QueryDescription
GetCurrentEraActive era (Byron through Conway)
GetChainBlockNoCurrent chain height, WithOrigin encoded as [1, blockNo] for At or [0] for Origin
GetChainPointCurrent tip point, encoded as [] for Origin or [slot, hash] for a specific point
GetSystemStartNetwork genesis time as UTCTime encoded [year, dayOfYear, picosOfDay]
GetEraHistoryIndefinite 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's EncCBOR instance (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:

MessageDescription
MsgSubmitTxSubmit a transaction (era ID + CBOR bytes)
MsgAcceptTxTransaction accepted into mempool
MsgRejectTxTransaction rejected with reason

Submitted transactions undergo both Phase-1 (structural) and Phase-2 (Plutus script) validation before mempool admission.

LocalTxMonitor

Monitors the transaction mempool:

MessageDescription
MsgAcquireAcquire a mempool snapshot
MsgHasTxCheck if a transaction is in the mempool
MsgNextTxGet the next transaction from the mempool
MsgGetSizesGet 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-key is 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 classifies peers into three temperature categories following the cardano-node model:

  • Cold — Known but not connected
  • 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 (5s dwell)
    Hot --> Warm: Demotion (poor performance / churn)
    Warm --> Cold: Disconnection / backoff
    Cold --> [*]: Evicted (max failures)

Warm peers must dwell for at least 5 seconds before promotion to Hot, preventing rapid cycling.

Peer Sources

Peers enter the Cold pool from four sources:

SourceDescription
TopologyBootstrap peers, local roots, and public roots from the topology file
DNSA/AAAA resolution of hostname-based topology entries
LedgerSPO relay addresses from pool registration certificates (after useLedgerAfterSlot)
PeerSharingAddresses 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 score1 / (1 + ms/200), based on EWMA latency (smoothing α=0.3)
  • Failure scoremax(1.0 - failures×0.1, 0.0), failure counts decay (halve every 5 minutes)

Subnet diversity is enforced: peers from the same /24 (IPv4) or /48 (IPv6) subnet receive a selection penalty.

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
  • Circuit breaker: Closed → Open → HalfOpen with exponential cooldown

Inbound Connections

  • Per-IP token bucket rate limiting for DoS protection
  • N2N server handles handshake, ChainSync, BlockFetch, KeepAlive, TxSubmission2, and PeerSharing
  • DiffusionMode controls whether inbound connections are accepted

P2P Governor

The governor runs as a tokio task on a 30-second interval, continuously evaluating peer counts against configured targets and emitting promotion/demotion/connect/disconnect actions.

Target Counts

The governor maintains six independent target counts (matching cardano-node defaults):

TargetDefaultDescription
TargetNumberOfKnownPeers85Total peers in the peer table (cold + warm + hot)
TargetNumberOfEstablishedPeers40Warm + hot peers (TCP connected)
TargetNumberOfActivePeers15Hot peers (fully syncing)
TargetNumberOfKnownBigLedgerPeers15Known big ledger peers
TargetNumberOfEstablishedBigLedgerPeers10Established big ledger peers
TargetNumberOfActiveBigLedgerPeers5Active big ledger peers

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:

  • Configurable churn interval (default: 20% target reduction cycle)
  • Local root peers are exempt from churn
  • Churn ensures the node explores the peer landscape rather than settling on suboptimal connections

Prometheus Metrics

The P2P subsystem exports the following metrics:

MetricDescription
dugite_p2p_enabledWhether P2P governance is active (gauge: 0 or 1)
dugite_diffusion_modeCurrent diffusion mode (0=InitiatorOnly, 1=InitiatorAndResponder)
dugite_peer_sharing_enabledWhether peer sharing is active (gauge: 0 or 1)
dugite_peers_coldNumber of cold (known, unconnected) peers
dugite_peers_warmNumber of warm (established) peers
dugite_peers_hotNumber of hot (active) peers

Peer Discovery

Peers are discovered through multiple channels:

  1. Topology file — Bootstrap peers, local roots, and public roots
  2. PeerSharing protocol — Gossip-based discovery from connected peers
  3. 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:

  1. 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.
  2. Relay extraction — All registered pool parameters are iterated, extracting relay entries of three types:
    • SingleHostAddr — IPv4 address and port
    • SingleHostName — DNS hostname and port
    • MultiHostName — DNS hostname with default port 3001
  3. 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.
  4. DNS resolution — Hostnames are resolved to socket addresses via async DNS lookup.
  5. Peer manager integration — Resolved addresses are added as cold peers with PeerSource::Ledger classification, 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 64 announcements. The architecture has three components:

  1. 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.
  2. Receivers — Each N2N server connection spawns with its own broadcast::Receiver subscription. The connection handler uses tokio::select! to concurrently service mini-protocol messages and listen for block announcements.
  3. Delivery — When a downstream peer is waiting at the tip (having received MsgAwaitReply from ChainSync), an incoming block announcement triggers a MsgRollForward message 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 IDMini-Protocol
0Handshake
2ChainSync (N2N)
3BlockFetch (N2N)
4TxSubmission2 (N2N)
8KeepAlive (N2N)
10PeerSharing (N2N)
5LocalChainSync (N2C)
6LocalTxSubmission (N2C)
7LocalStateQuery (N2C)
9LocalTxMonitor (N2C)

The multiplexer uses length-prefixed frames with protocol ID headers, matching the Ouroboros specification.