Introduction

Torsten is a Cardano node implementation written in Rust, aiming for 100% compatibility with cardano-node (Haskell).

Built by Sandstone Pool.

CI

Why Torsten?

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, and Conway eras.
  • Conway governance (CIP-1694) -- DRep registration, voting, proposals, constitutional committee, treasury withdrawals.
  • Pipelined multi-peer sync -- Header collection from a primary peer with parallel block fetching from multiple peers.
  • Plutus script execution -- Plutus V1/V2/V3 evaluation via the uplc CEK machine.
  • 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 LocalStateQuery, LocalTxSubmission, and LocalTxMonitor.
  • cardano-cli compatible CLI -- Key generation, transaction building, signing, submission, queries, and governance commands.
  • Prometheus metrics -- Real-time node metrics on port 12798.
  • P2P networking -- Peer manager with cold/warm/hot lifecycle, DNS multi-resolution, ledger-based peer discovery, and inbound rate limiting.
  • Mithril snapshot import -- Fast initial sync by importing a Mithril-certified snapshot.
  • SIGHUP topology reload -- Update peer configuration without restarting the node.

Project Status

Torsten 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.

See the Feature Status section in the repository README for a detailed checklist of implemented and pending features.

License

Torsten is released under the MIT License.

Installation

Torsten is built from source using the standard Rust toolchain. Pre-built binaries are not yet distributed.

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

Torsten requires Rust 1.75 or later (edition 2021).

System Dependencies

Torsten uses RocksDB for persistent storage, which requires libclang for compilation.

Ubuntu / Debian:

sudo apt-get update
sudo apt-get install -y libclang-dev build-essential pkg-config

macOS (Homebrew):

brew install llvm

The llvm package includes libclang. Homebrew typically configures the paths automatically. If not, you may need to set:

export LIBCLANG_PATH="$(brew --prefix llvm)/lib"

Fedora / RHEL:

sudo dnf install clang-devel

Arch Linux:

sudo pacman -S clang

Building from Source

Clone the repository:

git clone https://github.com/michaeljfazio/torsten.git
cd torsten

Build in release mode:

cargo build --release

This produces two binaries in target/release/:

BinaryDescription
torsten-nodeThe Cardano node
torsten-cliThe cardano-cli compatible command-line interface

Install Binaries

To install the binaries into your $CARGO_HOME/bin (typically ~/.cargo/bin/):

cargo install --path crates/torsten-node
cargo install --path crates/torsten-cli

Running Tests

Verify everything is working:

cargo test --all

The project enforces a zero-warning policy. You can run the full CI check locally:

cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all

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 building Torsten and syncing against the Cardano preview testnet.

1. Build

git clone https://github.com/michaeljfazio/torsten.git
cd torsten
cargo build --release

2. Create Configuration Files

Create a config-preview.json:

{
  "Network": "Testnet",
  "NetworkMagic": 2
}

Create a topology-preview.json with preview testnet relays:

{
  "bootstrapPeers": [
    {
      "address": "preview-node.play.dev.cardano.org",
      "port": 3001
    }
  ],
  "localRoots": [{ "accessPoints": [], "advertise": false, "valency": 1 }],
  "publicRoots": [{ "accessPoints": [], "advertise": false }],
  "useLedgerAfterSlot": 102729600
}

Tip: You can also download the official topology directly from the Cardano Operations Book.

3. Run the Node

./target/release/torsten-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

The node will:

  1. Load the configuration and topology
  2. Connect to the preview testnet bootstrap peers
  3. Perform the N2N handshake (protocol version V14+)
  4. Begin syncing blocks

Progress is logged every 5 seconds, showing:

  • Current slot and block number
  • Epoch number
  • UTxO count
  • Sync percentage
  • Blocks-per-second throughput

4. Query the Node

Once the node is running, you can query it using the CLI via the Unix domain socket:

# Query the current tip
./target/release/torsten-cli query tip \
  --socket-path ./node-preview.sock \
  --testnet-magic 2

Example output:

{
    "slot": 73429851,
    "hash": "a1b2c3d4...",
    "block": 2847392,
    "epoch": 170,
    "era": "Conway",
    "syncProgress": "95.42"
}

5. Fast Sync with Mithril (Optional)

To significantly reduce initial sync time, you can import a Mithril-certified snapshot before starting the node:

./target/release/torsten-node mithril-import \
  --network-magic 2 \
  --database-path ./db-preview

This downloads the latest snapshot from the Mithril aggregator, extracts it, and imports all blocks into the database. After the import completes, start the node normally and it will resume syncing from where the snapshot left off.

Next Steps

Configuration

Torsten 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):

{
  "Network": "Testnet",
  "NetworkMagic": 2,
  "EnableP2P": true,
  "Protocol": {
    "RequiresNetworkMagic": "RequiresMagic"
  },
  "ShelleyGenesisFile": "shelley-genesis.json",
  "ByronGenesisFile": "byron-genesis.json",
  "AlonzoGenesisFile": "alonzo-genesis.json",
  "ConwayGenesisFile": "conway-genesis.json",
  "TargetNumberOfActivePeers": 20,
  "TargetNumberOfEstablishedPeers": 40,
  "TargetNumberOfKnownPeers": 100,
  "MinSeverity": "Info",
  "TraceOptions": {
    "TraceBlockFetchClient": false,
    "TraceBlockFetchServer": false,
    "TraceChainDb": false,
    "TraceChainSyncClient": false,
    "TraceChainSyncServer": false,
    "TraceForge": false,
    "TraceMempool": false
  }
}

Fields Reference

Network Settings

FieldTypeDefaultDescription
Networkstring"Mainnet"Network identifier: "Mainnet" or "Testnet"
NetworkMagicintegerautoNetwork magic number. If omitted, derived from Network (764824073 for mainnet)
EnableP2PbooleantrueEnable P2P networking mode

Protocol

FieldTypeDefaultDescription
Protocol.RequiresNetworkMagicstring"RequiresMagic"Whether network magic is required in handshake

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", Torsten will look for /opt/cardano/shelley-genesis.json.

FieldTypeDefaultDescription
ShelleyGenesisFilestringnonePath to Shelley genesis JSON
ByronGenesisFilestringnonePath to Byron genesis JSON
AlonzoGenesisFilestringnonePath to Alonzo genesis JSON
ConwayGenesisFilestringnonePath to Conway genesis JSON

Tip: Genesis files for each network can be downloaded from the Cardano Operations Book.

P2P Parameters

FieldTypeDefaultDescription
TargetNumberOfActivePeersinteger20Target number of active (hot) peers
TargetNumberOfEstablishedPeersinteger40Target number of established (warm) peers
TargetNumberOfKnownPeersinteger100Target number of known (cold) peers

Tracing

FieldTypeDefaultDescription
MinSeveritystring"Info"Minimum log severity level
TraceOptions.TraceBlockFetchClientbooleanfalseTrace block fetch client activity
TraceOptions.TraceBlockFetchServerbooleanfalseTrace block fetch server activity
TraceOptions.TraceChainDbbooleanfalseTrace ChainDB operations
TraceOptions.TraceChainSyncClientbooleanfalseTrace chain sync client activity
TraceOptions.TraceChainSyncServerbooleanfalseTrace chain sync server activity
TraceOptions.TraceForgebooleanfalseTrace block forging
TraceOptions.TraceMempoolbooleanfalseTrace mempool activity

Log Level Control

Torsten uses the RUST_LOG environment variable for fine-grained log control:

# Default (info level)
RUST_LOG=info torsten-node run ...

# Debug level for all crates
RUST_LOG=debug torsten-node run ...

# Debug only for specific crates
RUST_LOG=torsten_network=debug,torsten_consensus=debug torsten-node run ...

# Trace level for detailed diagnostics
RUST_LOG=trace torsten-node run ...

Minimal Configuration

The smallest viable configuration file specifies only the network:

{
  "Network": "Testnet",
  "NetworkMagic": 2
}

All other fields use sensible defaults. When no genesis files are specified, the node operates with built-in default parameters.

Format Support

Torsten supports both JSON (.json) and TOML (.toml) configuration files. The format is determined by the file extension. JSON files use the cardano-node compatible PascalCase format shown above.

Topology

The topology file defines the peers that the node connects to. Torsten 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": 177724800
}

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 }
]

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"
  }
]
FieldTypeDefaultDescription
accessPointsarrayrequiredList of {address, port} entries
advertisebooleanfalseWhether to share these peers via peer sharing protocol
valencyinteger1Deprecated. Target number of active connections. Use hotValency instead
hotValencyintegervalencyTarget number of hot (actively syncing) peers
warmValencyintegerhotValency+1Target number of warm (connected, not syncing) peers
trustablebooleanfalseWhether these peers are trusted for sync. Trusted peers are preferred during initial sync
behindFirewallbooleanfalseIf true, the node waits for inbound connections from these peers instead of connecting outbound
diffusionModestring"InitiatorAndResponder"Per-group diffusion mode. "InitiatorOnly" for unidirectional connections

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.

Peer Snapshot File

Optional path to a big ledger peer snapshot file for Genesis bootstrap:

"peerSnapshotFile": "peer-snapshot.json"

Example Topologies

Preview Testnet Relay

{
  "bootstrapPeers": [
    { "address": "preview-node.play.dev.cardano.org", "port": 3001 }
  ],
  "localRoots": [
    { "accessPoints": [], "advertise": false, "valency": 1 }
  ],
  "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": [], "advertise": false, "valency": 1 }
  ],
  "publicRoots": [
    { "accessPoints": [], "advertise": false }
  ],
  "useLedgerAfterSlot": 177724800
}

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
}

SIGHUP Topology Reload

Torsten 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 $(pidof torsten-node)

This allows you to add or remove peers without restarting the node.

Networks

Torsten can connect to any Cardano network. Each network is identified by a unique magic number used during the N2N handshake.

Network Magic Values

NetworkMagicDescription
Mainnet764824073The production Cardano network
Preview2Fast-moving testnet for early feature testing
Preprod1Stable testnet that mirrors mainnet behavior

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": [{ "accessPoints": [], "advertise": false, "valency": 1 }],
  "publicRoots": [{ "accessPoints": [], "advertise": false }],
  "useLedgerAfterSlot": 177724800
}

Run the node:

torsten-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.

Connecting to Preview Testnet

Create a config-preview.json:

{
  "Network": "Testnet",
  "NetworkMagic": 2
}

Create a topology-preview.json:

{
  "bootstrapPeers": [
    { "address": "preview-node.play.dev.cardano.org", "port": 3001 }
  ],
  "localRoots": [{ "accessPoints": [], "advertise": false, "valency": 1 }],
  "publicRoots": [{ "accessPoints": [], "advertise": false }],
  "useLedgerAfterSlot": 102729600
}

Run the node:

torsten-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, "valency": 1 }],
  "publicRoots": [{ "accessPoints": [], "advertise": false }],
  "useLedgerAfterSlot": 76924800
}

Run the node:

torsten-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:

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
torsten-cli query tip --socket-path ./node-preview.sock --testnet-magic 2

# Preprod
torsten-cli query tip --socket-path ./node-preprod.sock --testnet-magic 1

# Mainnet (default, --testnet-magic not needed)
torsten-cli query tip --socket-path ./node-mainnet.sock

Multiple Nodes

You can run multiple Torsten instances on the same machine by using different ports, database paths, and socket paths:

# Preview on port 3001
torsten-node run --port 3001 --database-path ./db-preview --socket-path ./preview.sock ...

# Preprod on port 3002
torsten-node run --port 3002 --database-path ./db-preprod --socket-path ./preprod.sock ...

Mithril Snapshot Import

Syncing a Cardano node from genesis can take a very long time. Torsten 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:

  1. Queries the Mithril aggregator for the latest available snapshot
  2. Downloads the snapshot archive (compressed with zstandard)
  3. Extracts the cardano-node chunk files
  4. Parses each block using the pallas CBOR decoder
  5. Bulk-imports blocks into Torsten's RocksDB-based ImmutableDB

Usage

torsten-node mithril-import \
  --network-magic <magic> \
  --database-path <path>

Arguments

ArgumentDefaultDescription
--network-magic764824073Network magic (764824073=mainnet, 2=preview, 1=preprod)
--database-pathdbPath to the database directory
--temp-dirsystem tempTemporary directory for download and extraction

Examples

Mainnet:

torsten-node mithril-import \
  --network-magic 764824073 \
  --database-path ./db-mainnet

Preview testnet:

torsten-node mithril-import \
  --network-magic 2 \
  --database-path ./db-preview

Preprod testnet:

torsten-node mithril-import \
  --network-magic 1 \
  --database-path ./db-preprod

Mithril Aggregator Endpoints

Torsten automatically selects the correct aggregator for each network:

NetworkAggregator URL
Mainnethttps://aggregator.release-mainnet.api.mithril.network/aggregator
Previewhttps://aggregator.pre-release-preview.api.mithril.network/aggregator
Preprodhttps://aggregator.release-preprod.api.mithril.network/aggregator

Resume Support

The import process supports resuming interrupted downloads and imports:

  • If the snapshot archive has already been downloaded (same size), the download is skipped
  • If the archive has already been extracted, extraction is skipped
  • Blocks already present in the database are skipped during import

This means you can safely interrupt the import and restart it later.

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:

torsten-node run \
  --config config.json \
  --topology topology.json \
  --database-path ./db-mainnet \
  --socket-path ./node.sock \
  --host-addr 0.0.0.0 \
  --port 3001

Disk Space Requirements

Mithril snapshots are large. Approximate sizes (which grow over time):

NetworkCompressed ArchiveExtractedFinal 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-dir flag can be used to direct temporary files to a different volume if needed.

Monitoring

Torsten exposes a Prometheus-compatible metrics endpoint for monitoring node health and sync progress.

Metrics Endpoint

The metrics server runs on port 12798 by default and responds to any HTTP request with Prometheus exposition format metrics:

http://localhost:12798/metrics

Example response:

# HELP torsten_blocks_received_total Total blocks received from peers
# TYPE torsten_blocks_received_total gauge
torsten_blocks_received_total 1523847

# HELP torsten_blocks_applied_total Total blocks applied to ledger
# TYPE torsten_blocks_applied_total gauge
torsten_blocks_applied_total 1523845

# HELP torsten_slot_number Current slot number
# TYPE torsten_slot_number gauge
torsten_slot_number 142857392

# HELP torsten_block_number Current block number
# TYPE torsten_block_number gauge
torsten_block_number 11283746

# HELP torsten_epoch_number Current epoch number
# TYPE torsten_epoch_number gauge
torsten_epoch_number 512

# HELP torsten_sync_progress_percent Chain sync progress (0-10000, divide by 100 for %)
# TYPE torsten_sync_progress_percent gauge
torsten_sync_progress_percent 9542

# HELP torsten_utxo_count Number of entries in the UTxO set
# TYPE torsten_utxo_count gauge
torsten_utxo_count 15234892

# HELP torsten_mempool_tx_count Number of transactions in the mempool
# TYPE torsten_mempool_tx_count gauge
torsten_mempool_tx_count 42

# HELP torsten_peers_connected Number of connected peers
# TYPE torsten_peers_connected gauge
torsten_peers_connected 8

Available Metrics

MetricTypeDescription
torsten_blocks_received_totalcounterTotal blocks received from peers
torsten_blocks_applied_totalcounterTotal blocks successfully applied to the ledger
torsten_transactions_received_totalcounterTotal transactions received
torsten_transactions_validated_totalcounterTotal transactions validated
torsten_transactions_rejected_totalcounterTotal transactions rejected
torsten_peers_connectedgaugeNumber of connected peers
torsten_peers_coldgaugeNumber of cold (known but unconnected) peers
torsten_peers_warmgaugeNumber of warm (connected, not syncing) peers
torsten_peers_hotgaugeNumber of hot (actively syncing) peers
torsten_sync_progress_percentgaugeChain sync progress (0-10000; divide by 100 for percentage)
torsten_slot_numbergaugeCurrent slot number
torsten_block_numbergaugeCurrent block number
torsten_epoch_numbergaugeCurrent epoch number
torsten_utxo_countgaugeNumber of entries in the UTxO set
torsten_mempool_tx_countgaugeNumber of transactions in the mempool
torsten_mempool_bytesgaugeSize of the mempool in bytes
torsten_rollback_count_totalcounterTotal number of chain rollbacks
torsten_blocks_forged_totalcounterTotal blocks forged by this node
torsten_delegation_countgaugeNumber of active stake delegations
torsten_treasury_lovelacegaugeTotal lovelace in the treasury

Prometheus Configuration

Add the Torsten node as a scrape target in your prometheus.yml:

scrape_configs:
  - job_name: 'torsten'
    scrape_interval: 15s
    static_configs:
      - targets: ['localhost:12798']
        labels:
          network: 'mainnet'
          node: 'relay-1'

Grafana Dashboard

You can create a Grafana dashboard to visualize Torsten metrics. Key panels to consider:

  • Sync Progress: torsten_sync_progress_percent / 100 (percentage)
  • Block Height: torsten_block_number
  • Current Epoch: torsten_epoch_number
  • Blocks/sec throughput: rate(torsten_blocks_applied_total[5m])
  • UTxO Set Size: torsten_utxo_count
  • Mempool Size: torsten_mempool_tx_count
  • Connected Peers: torsten_peers_connected
  • Peer States: torsten_peers_cold, torsten_peers_warm, torsten_peers_hot
  • Transaction Rejection Rate: rate(torsten_transactions_rejected_total[5m])
  • Blocks Forged: torsten_blocks_forged_total
  • Rollback Count: torsten_rollback_count_total
  • Active Delegations: torsten_delegation_count
  • Treasury Balance: torsten_treasury_lovelace / 1e6 (in ADA)

Console Logging

In addition to the Prometheus endpoint, Torsten logs sync progress to the console every 5 seconds. The log output includes:

  • Current slot and block number
  • Epoch number
  • UTxO count
  • Sync percentage
  • Blocks-per-second throughput

Example log line:

INFO torsten_node::node: slot=142857392 block=11283746 epoch=512 utxo=15234892 sync=95.42% speed=312 blk/s

Set the log level via the RUST_LOG environment variable:

RUST_LOG=info torsten-node run ...

Block Producer

Torsten can operate as a block-producing node (stake pool). This requires KES keys, VRF keys, and an operational certificate.

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:

  1. 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.
  2. Block forging -- If elected, the node assembles a block from pending mempool transactions, signs it with the KES key, and includes the VRF proof.
  3. 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:

torsten-cli node key-gen \
  --cold-verification-key-file cold.vkey \
  --cold-signing-key-file cold.skey \
  --operational-certificate-issue-counter-file opcert.counter

KES Keys (Hot)

KES (Key Evolving Signature) keys are rotated periodically. Each KES key is valid for a limited number of KES periods (typically 62 periods of 129600 slots each on mainnet, approximately 90 days total).

Generate KES keys:

torsten-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:

torsten-cli node key-gen-VRF \
  --verification-key-file vrf.vkey \
  --signing-key-file vrf.skey

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:

torsten-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

On mainnet, slots_per_kes_period is 129600.

Running as Block Producer

Pass the key and certificate paths when starting the node:

torsten-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.

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: -1 disables 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 for an epoch:

torsten-cli query leadership-schedule \
  --vrf-signing-key-file vrf.skey \
  --epoch-nonce <64-char-hex> \
  --epoch-start-slot <slot> \
  --epoch-length 432000 \
  --relative-stake 0.001 \
  --active-slot-coeff 0.05

This outputs all slots where your pool is elected to produce a block in the given epoch.

KES Key Rotation

KES keys must be rotated before they expire. The rotation process:

  1. Generate new KES keys:

    torsten-cli node key-gen-KES \
      --verification-key-file kes-new.vkey \
      --signing-key-file kes-new.skey
    
  2. Issue a new operational certificate with the new KES key (on the air-gapped machine):

    torsten-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.cert
    
  3. Replace the KES key and certificate on the block producer and restart:

    cp kes-new.skey kes.skey
    cp opcert-new.cert opcert.cert
    # Restart the node
    

Important: Always rotate KES keys before they expire. If a KES key expires, your pool will stop producing blocks until a new key is issued.

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 torsten_blocks_applied_total.
  • 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.

CLI Overview

Torsten provides torsten-cli, a cardano-cli compatible command-line interface for interacting with a running Torsten node and managing keys, transactions, and governance.

Binary

torsten-cli [COMMAND] [OPTIONS]

Command Groups

CommandDescription
addressAddress generation and manipulation
keyPayment and stake key generation
transactionTransaction building, signing, and submission
queryNode queries (tip, UTxO, protocol parameters, etc.)
stake-addressStake address registration, delegation, and vote delegation
stake-poolStake pool operations (retirement certificates)
governanceConway governance (DRep, voting, proposals)
nodeNode key operations (cold keys, KES, VRF, operational certificates)

Common Patterns

Socket Path

Most commands that interact with a running node require --socket-path to specify the Unix domain socket:

torsten-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:

torsten-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:

torsten-cli transaction build ... --out-file tx.body
torsten-cli transaction sign ... --out-file tx.signed

Help

Every command supports --help:

torsten-cli --help
torsten-cli transaction --help
torsten-cli transaction build --help

Key Generation

Torsten CLI supports generating all key types needed for Cardano operations.

Payment Keys

Generate an Ed25519 key pair for payments:

torsten-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)

Stake Keys

Generate an Ed25519 key pair for staking:

torsten-cli key generate-stake-key \
  --signing-key-file stake.skey \
  --verification-key-file stake.vkey

Output files:

  • stake.skey -- Stake signing key
  • stake.vkey -- Stake verification key

Verification Key Hash

Compute the Blake2b-224 hash of any verification key:

torsten-cli key verification-key-hash \
  --verification-key-file payment.vkey

This outputs the 28-byte key hash in hexadecimal, used in addresses and certificates.

DRep Keys

Generate keys for a Delegated Representative (Conway governance):

torsten-cli governance drep key-gen \
  --signing-key-file drep.skey \
  --verification-key-file drep.vkey

Get the DRep ID:

# Bech32 format (default)
torsten-cli governance drep id \
  --drep-verification-key-file drep.vkey

# Hex format
torsten-cli governance drep id \
  --drep-verification-key-file drep.vkey \
  --output-format hex

Node Keys

Cold Keys

Generate cold keys and an operational certificate issue counter:

torsten-cli node key-gen \
  --cold-verification-key-file cold.vkey \
  --cold-signing-key-file cold.skey \
  --operational-certificate-issue-counter-file opcert.counter

KES Keys

Generate Key Evolving Signature keys (rotated periodically):

torsten-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):

torsten-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:

torsten-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)
torsten-cli address build \
  --payment-verification-key-file payment.vkey \
  --testnet-magic 2

# Base address (with staking)
torsten-cli address build \
  --payment-verification-key-file payment.vkey \
  --stake-verification-key-file stake.vkey \
  --testnet-magic 2

# Mainnet address
torsten-cli address build \
  --payment-verification-key-file payment.vkey \
  --stake-verification-key-file stake.vkey \
  --mainnet

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 Torsten are compatible with cardano-cli and vice versa.

Complete Workflow Example

Generate all keys needed for a basic wallet:

# 1. Generate payment keys
torsten-cli key generate-payment-key \
  --signing-key-file payment.skey \
  --verification-key-file payment.vkey

# 2. Generate stake keys
torsten-cli key generate-stake-key \
  --signing-key-file stake.skey \
  --verification-key-file stake.vkey

# 3. Build a testnet address
torsten-cli address build \
  --payment-verification-key-file payment.vkey \
  --stake-verification-key-file stake.vkey \
  --testnet-magic 2

# 4. Get the payment key hash
torsten-cli key verification-key-hash \
  --verification-key-file payment.vkey

Transactions

Torsten CLI supports the full transaction lifecycle: building, signing, submitting, and inspecting transactions.

Building a Transaction

torsten-cli transaction build \
  --tx-in <tx_hash>#<index> \
  --tx-out <address>+<lovelace> \
  --change-address <address> \
  --fee <lovelace> \
  --out-file tx.body

Arguments

ArgumentDescription
--tx-inTransaction input in tx_hash#index format. Can be specified multiple times
--tx-outTransaction output in address+lovelace format. Can be specified multiple times
--change-addressAddress to receive change
--feeFee in lovelace (default: 200000)
--ttlTime-to-live slot number (optional)
--certificate-filePath to a certificate file to include (can be repeated)
--withdrawalWithdrawal in stake_address+lovelace format (can be repeated)
--metadata-json-filePath to a JSON metadata file (optional)
--out-fileOutput file for the transaction body

Example: Simple ADA Transfer

torsten-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:

torsten-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

torsten-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!"]
  }
}
torsten-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

torsten-cli transaction sign \
  --tx-body-file tx.body \
  --signing-key-file payment.skey \
  --out-file tx.signed

Multiple signing keys can be provided:

torsten-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

torsten-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

torsten-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:

torsten-cli transaction txid --tx-file tx.body

Works with both transaction body files and signed transaction files.

Calculate Minimum Fee

torsten-cli transaction calculate-min-fee \
  --tx-body-file tx.body \
  --witness-count 2 \
  --protocol-params-file protocol-params.json

The fee is calculated as: fee = txFeePerByte * tx_size + txFeeFixed

To get the current protocol parameters:

torsten-cli query protocol-parameters \
  --socket-path ./node.sock \
  --out-file protocol-params.json

Creating Witnesses

For multi-signature workflows, you can create witnesses separately and assemble them:

Create a Witness

torsten-cli transaction witness \
  --tx-body-file tx.body \
  --signing-key-file payment.skey \
  --out-file payment.witness

Assemble a Transaction

torsten-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:

torsten-cli transaction policyid --script-file policy.script

Complete Workflow

# 1. Query UTxOs to find inputs
torsten-cli query utxo \
  --address addr_test1qz... \
  --socket-path ./node.sock \
  --testnet-magic 2

# 2. Get protocol parameters for fee calculation
torsten-cli query protocol-parameters \
  --socket-path ./node.sock \
  --testnet-magic 2 \
  --out-file pp.json

# 3. Build the transaction
torsten-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
torsten-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
torsten-cli transaction sign \
  --tx-body-file tx.body \
  --signing-key-file payment.skey \
  --out-file tx.signed

# 7. Submit
torsten-cli transaction submit \
  --tx-file tx.signed \
  --socket-path ./node.sock

Queries

Torsten 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:

torsten-cli query tip --socket-path ./node.sock

For testnets:

torsten-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:

torsten-cli query utxo \
  --address addr_test1qz... \
  --socket-path ./node.sock \
  --testnet-magic 2

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
torsten-cli query protocol-parameters \
  --socket-path ./node.sock

# Save to file
torsten-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:

torsten-cli query stake-distribution \
  --socket-path ./node.sock

Output:

PoolId                                                             Stake (lovelace)   Pledge (lovelace)
----------------------------------------------------------------------------------------------------------
pool1abc...                                                        15234892000000      500000000000
pool1def...                                                         8923451000000      250000000000

Total pools: 3200

Stake Address Info

Query delegation and rewards for a stake address:

torsten-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 all registered stake pools with their parameters:

torsten-cli query stake-pools \
  --socket-path ./node.sock

Output:

PoolId                                                      Pledge (ADA)    Cost (ADA)   Margin
----------------------------------------------------------------------------------------------------
pool1abc...                                                   500.000000     340.000000    1.00%
pool1def...                                                   250.000000     340.000000    2.50%

Total pools: 3200

Pool Parameters

Query detailed parameters for a specific pool:

torsten-cli query pool-params \
  --socket-path ./node.sock \
  --stake-pool-id pool1abc...

Stake Snapshots

Query the mark/set/go stake snapshots:

torsten-cli query stake-snapshot \
  --socket-path ./node.sock

# Filter by pool
torsten-cli query stake-snapshot \
  --socket-path ./node.sock \
  --stake-pool-id pool1abc...

Governance State (Conway)

Query the overall governance state:

torsten-cli query gov-state --socket-path ./node.sock

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:

# All DReps
torsten-cli query drep-state --socket-path ./node.sock

# Specific DRep by key hash
torsten-cli query drep-state \
  --socket-path ./node.sock \
  --drep-key-hash a1b2c3d4...

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:

torsten-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)
torsten-cli query tx-mempool info --socket-path ./node.sock

# Check if a specific transaction is in the mempool
torsten-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

Leadership Schedule

Compute the leader schedule for a stake pool:

torsten-cli query leadership-schedule \
  --vrf-signing-key-file vrf.skey \
  --epoch-nonce a1b2c3d4... \
  --epoch-start-slot 73000000 \
  --epoch-length 432000 \
  --relative-stake 0.001 \
  --active-slot-coeff 0.05

Output:

Computing leader schedule for epoch starting at slot 73000000...
Epoch length: 432000 slots
Relative stake: 0.001000
Active slot coefficient: 0.05

SlotNo       VRF Output (first 16 bytes)
--------------------------------------------------
73012345     a1b2c3d4e5f6a7b8...
73045678     d4e5f6a7b8c9d0e1...

Total leader slots: 2
Expected: ~22 (f=0.05, stake=0.001000)

Governance

Torsten 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

torsten-cli governance drep key-gen \
  --signing-key-file drep.skey \
  --verification-key-file drep.vkey

Get DRep ID

# Bech32 format (default)
torsten-cli governance drep id \
  --drep-verification-key-file drep.vkey

# Hex format
torsten-cli governance drep id \
  --drep-verification-key-file drep.vkey \
  --output-format hex

DRep Registration

Create a DRep registration certificate:

torsten-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

torsten-cli governance drep retirement-certificate \
  --drep-verification-key-file drep.vkey \
  --deposit-amt 500000000 \
  --out-file drep-retire.cert

DRep Update

Update DRep metadata:

torsten-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:

torsten-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:

torsten-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:

torsten-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

ValueDescription
yesVote in favor
noVote against
abstainAbstain from voting

Vote with Anchor

Attach rationale metadata to a vote:

torsten-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):

torsten-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:

torsten-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:

torsten-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:

torsten-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:

torsten-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 \
  --out-file pp-update.json

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:

torsten-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:

torsten-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
torsten-cli governance action hash-anchor-data \
  --file-binary proposal.json

# Text file
torsten-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
torsten-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

torsten-cli transaction sign \
  --tx-body-file tx.body \
  --signing-key-file payment.skey \
  --signing-key-file drep.skey \
  --out-file tx.signed

torsten-cli transaction submit \
  --tx-file tx.signed \
  --socket-path ./node.sock

Architecture Overview

Torsten is organized as a 10-crate Cargo workspace. Each crate has a focused responsibility and well-defined dependencies.

Crate Workspace

CrateDescription
torsten-primitivesCore types: hashes, blocks, transactions, addresses, values, protocol parameters (Byron--Conway)
torsten-cryptoEd25519 keys, VRF, KES, text envelope format
torsten-serializationCBOR encoding/decoding for Cardano wire format via pallas
torsten-networkOuroboros mini-protocols (ChainSync, BlockFetch, TxSubmission, KeepAlive), N2N client/server, N2C server, multi-peer block fetch pool
torsten-consensusOuroboros Praos, chain selection, epoch transitions, slot leader checks
torsten-ledgerUTxO set, transaction validation, ledger state, certificate processing, native script evaluation, reward calculation
torsten-mempoolThread-safe transaction mempool
torsten-storageChainDB (ImmutableDB via RocksDB with WriteBatch + VolatileDB in-memory)
torsten-nodeMain binary, config, topology, pipelined chain sync loop
torsten-clicardano-cli compatible CLI

Crate Dependency Graph

graph TD
    NODE[torsten-node] --> NET[torsten-network]
    NODE --> CONS[torsten-consensus]
    NODE --> LEDGER[torsten-ledger]
    NODE --> STORE[torsten-storage]
    NODE --> POOL[torsten-mempool]
    CLI[torsten-cli] --> NET
    CLI --> PRIM[torsten-primitives]
    CLI --> CRYPTO[torsten-crypto]
    CLI --> SER[torsten-serialization]
    NET --> PRIM
    NET --> CRYPTO
    NET --> SER
    NET --> POOL
    CONS --> PRIM
    CONS --> CRYPTO
    LEDGER --> PRIM
    LEDGER --> CRYPTO
    LEDGER --> SER
    STORE --> PRIM
    STORE --> SER
    POOL --> PRIM
    SER --> PRIM
    CRYPTO --> PRIM

Key Dependencies

Torsten leverages the pallas family of crates (v1.0.0-alpha.5) for Cardano wire-format compatibility:

  • pallas-network -- Ouroboros multiplexer and handshake
  • pallas-codec -- CBOR encoding/decoding
  • pallas-primitives -- Cardano primitive types
  • pallas-traverse -- Multi-era block traversal
  • pallas-crypto -- Cryptographic primitives
  • pallas-addresses -- Address parsing and construction

Other key dependencies:

  • tokio -- Async runtime
  • rocksdb -- Persistent storage for the immutable database
  • minicbor -- CBOR encoding for custom types
  • ed25519-dalek -- Ed25519 signatures
  • blake2 -- Blake2b hashing
  • uplc -- Plutus CEK machine for script evaluation
  • 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.

Pallas Interoperability

Torsten uses pallas for network protocol handling and block deserialization, ensuring wire-format compatibility with cardano-node. Internal types (in torsten-primitives) are converted from pallas types during deserialization.

Key conversion patterns:

  • Transaction.hash is set during deserialization from pallas tx.hash()
  • ChainSyncEvent::RollForward uses Box<Block> to avoid large enum variant size
  • Invalid transactions (is_valid: false) are skipped during apply_block
  • Pool IDs are Hash28 (Blake2b-224), not Hash32

Multi-Era Support

Torsten handles all Cardano eras from Byron through Conway. The serialization layer handles era-specific block formats transparently, while the ledger layer applies era-appropriate validation rules.

Sync Pipeline

Torsten uses a pipelined multi-peer architecture for block synchronization, separating header collection from block fetching for maximum throughput.

Architecture

flowchart LR
    subgraph Primary Peer
        CS[ChainSync<br/>Header Collection]
    end

    CS -->|headers| HQ[Header Queue]

    subgraph Block Fetch Pool
        BF1[Peer 1<br/>BlockFetch]
        BF2[Peer 2<br/>BlockFetch]
        BF3[Peer N<br/>BlockFetch]
    end

    HQ -->|range 1| BF1
    HQ -->|range 2| BF2
    HQ -->|range N| BF3

    BF1 -->|blocks| BP[Block Processor]
    BF2 -->|blocks| BP
    BF3 -->|blocks| BP

    BP --> CDB[(ChainDB)]
    BP --> LS[Ledger State]

Pipeline Stages

1. Header Collection (ChainSync)

A primary peer is selected for the ChainSync protocol. The node requests block headers sequentially using the N2N ChainSync mini-protocol (V14+). Headers are collected into batches.

The ChainSync protocol involves:

  1. MsgFindIntersect -- Find a common point between the node and the peer
  2. MsgRequestNext -- Request the next header
  3. MsgRollForward -- Receive a new header
  4. MsgRollBackward -- Handle a chain reorganization

2. Block Fetch Pool

Collected headers are distributed across multiple peers for parallel block retrieval. The block fetch pool supports up to 4 concurrent peers, each fetching a range of blocks.

The BlockFetch protocol involves:

  1. MsgRequestRange -- Request a range of blocks by header hash
  2. MsgBlock -- Receive a block
  3. MsgBatchDone -- Signal the end of a batch

Blocks are fetched in batches of 500 headers, with sub-batches of 100 headers each. Each sub-batch is decoded on a spawn_blocking task to avoid blocking the async runtime.

3. Block Processing

Fetched blocks are applied to the ledger state in order:

  1. Deserialization -- Raw CBOR bytes are decoded into Torsten's internal Block type using pallas
  2. Ledger validation -- Each block is validated against the current ledger state (UTxO checks, fee validation, certificate processing)
  3. Storage -- Valid blocks are added to the ChainDB (volatile database first, flushed to immutable when k-deep)
  4. Epoch transitions -- At epoch boundaries, stake snapshots are rotated and rewards are calculated

Batched Lock Acquisition

To minimize lock contention, the sync loop acquires a single lock on both the ChainDB and ledger state for each batch of 500 blocks, rather than locking per-block.

Progress Reporting

Progress is logged every 5 seconds, 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:

  1. Identifies the rollback point (a slot/hash pair)
  2. Removes rolled-back blocks from the VolatileDB
  3. Reverts the ledger state to the rollback point
  4. 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.

Performance Characteristics

  • Header collection is serial per peer due to the ChainSync protocol design (~300ms per header request for network RTT)
  • Block fetching is parallelized across multiple peers
  • Block processing is batched (500 blocks per batch) with single-lock acquisition
  • Throughput depends on network latency, peer count, and block sizes

The primary bottleneck during initial sync is the serial header-by-header ChainSync protocol. Multi-peer ChainSync (fetching headers from multiple peers simultaneously) is planned for future optimization.

Storage

Torsten's storage layer is implemented in the torsten-storage crate, centered around ChainDB -- a two-tier block storage system modeled after cardano-node's design.

Storage Architecture

flowchart TD
    CDB[ChainDB] --> VOL[VolatileDB<br/>In-Memory BTreeMap<br/>Last k=2160 blocks]
    CDB --> IMM[ImmutableDB<br/>RocksDB with WriteBatch<br/>Permanent 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

ChainDB

ChainDB is the unified interface for block storage. It manages two underlying databases:

  • VolatileDB -- Recent blocks that may be rolled back
  • ImmutableDB -- Permanent blocks that are considered final

Block Lifecycle

  1. New blocks arrive from peers and are added to the VolatileDB
  2. Once a block is more than k slots deep (k=2160 for mainnet), it is flushed from the VolatileDB to the ImmutableDB
  3. Flushed blocks are removed from the VolatileDB

Block Queries

When querying for a block:

  1. The VolatileDB is checked first (fast, in-memory)
  2. If not found, the ImmutableDB is consulted (disk-based)

Slot Range Queries

ChainDB supports querying blocks by slot range:

  • VolatileDB uses BTreeMap::range() for efficient slot-based lookups
  • ImmutableDB uses RocksDB iterators for slot range scanning
  • Results from both databases are merged

VolatileDB

The VolatileDB stores recent blocks in an in-memory BTreeMap indexed by slot number. This enables:

  • Fast reads -- No disk I/O for recent blocks
  • Efficient rollback -- Blocks can be removed without touching disk
  • Ordered iteration -- BTreeMap provides natural slot ordering

The VolatileDB holds the last k=2160 blocks (the security parameter). Once a block is deeper than k, it is considered immutable and flushed to the ImmutableDB.

ImmutableDB

The ImmutableDB uses RocksDB for persistent block storage. Key design choices:

WriteBatch

Blocks are written in batches using RocksDB's WriteBatch API. When the VolatileDB flushes blocks to the ImmutableDB, it creates a single WriteBatch containing all blocks to be flushed. This provides:

  • Atomicity -- All blocks in the batch are written together or not at all
  • Performance -- A single disk sync for multiple blocks
  • Consistency -- No partial writes on crash

Key Format

Blocks are stored with their slot number as the key, enabling efficient range scans for slot-based queries.

Metadata

The ImmutableDB tracks:

  • Tip slot -- The highest slot stored
  • Tip block number -- The highest block number stored
  • Tip hash -- The hash of the highest block

This metadata is persisted to enable tip recovery on restart.

Tip Recovery

When the node restarts, it recovers its tip from persisted metadata:

  1. The ImmutableDB tip is read from RocksDB metadata
  2. The VolatileDB starts empty (in-memory state is lost on restart)
  3. The node resumes syncing from the ImmutableDB tip

Ledger State Snapshots

In addition to block storage, the node periodically saves ledger state snapshots to disk. This allows the node to recover its full state (UTxO set, stake distribution, protocol parameters) without replaying all blocks from genesis.

Disk Layout

database-path/
  immutable/       # RocksDB database files
    000001.sst
    000002.sst
    ...
    MANIFEST-000001
    CURRENT
    LOG

The VolatileDB has no on-disk representation -- it exists only in memory.

Performance Considerations

  • Batch size -- The flush batch size balances memory usage (larger batches use more memory) against write efficiency (fewer disk syncs)
  • RocksDB tuning -- The default RocksDB configuration works well for most cases. The release profile uses opt-level = 3 and lto = "thin" for optimal RocksDB performance
  • Memory usage -- The VolatileDB holds approximately k blocks in memory. At ~2160 blocks, this is typically a few hundred MB depending on block sizes

Consensus

Torsten 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:

  1. A VRF output -- A deterministic pseudo-random value
  2. 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).

Epoch Nonce

The epoch nonce is computed at each epoch boundary:

nonce = hash(rolling_nonce || first_block_hash_prev_epoch)

Where:

  • rolling_nonce is updated per-block: hash(prev_eta_v || hash(vrf_output))
  • first_block_hash_prev_epoch is the hash of the first block in the previous epoch

The initial rolling nonce is derived from the Shelley genesis hash.

Chain Selection

When multiple valid chains exist, Ouroboros Praos selects the chain with the most blocks (longest chain rule). Torsten implements:

  1. Chain comparison -- Compare the block height of competing chains
  2. Rollback support -- Roll back up to k=2160 blocks to switch to a longer chain
  3. Immutability -- Blocks deeper than k are considered final

Epoch Transitions

At each epoch boundary, Torsten performs:

Stake Snapshot Rotation

Torsten uses the mark/set/go snapshot model:

  • Mark -- The current stake distribution (used 2 epochs in the future)
  • Set -- The previous mark (used 1 epoch in the future)
  • Go -- The active stake distribution for the current epoch

At each epoch boundary:

  1. Go becomes the active snapshot
  2. Set moves to go
  3. Mark moves to set
  4. A new mark is taken from the current ledger state

Reward Calculation and Distribution

At each epoch boundary, rewards are calculated and distributed:

  1. Monetary expansion -- New ADA is created from the reserves based on the monetary expansion rate
  2. Fee collection -- Transaction fees from the epoch are collected
  3. Treasury cut -- A fraction (tau) of rewards goes to the treasury
  4. Pool rewards -- Remaining rewards are distributed to pools based on their performance
  5. Member distribution -- Pool rewards are split between the operator and delegators based on pool parameters (cost, margin, pledge)

Validation Checks

Torsten 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:

  1. VRF key binding -- blake2b_256(header.vrf_vkey) must match the pool's registered vrf_keyhash
  2. VRF proof verification -- The VRF proof is cryptographically verified against the VRF public key
  3. 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). The KES key is evolved to the correct period offset from the operational certificate's start period. Verification checks:

  1. The KES signature over the header body bytes is valid
  2. 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

Torsten 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-V17 (Conway era) with automatic detection of the Haskell bit-15 version encoding used by cardano-cli.

LocalStateQuery

Supports a wide range of ledger queries:

QueryTagDescription
Chain tip0Current slot, hash, block number
Current epoch1Active epoch number
Current era--Active era (Byron through Conway)
Block number--Current chain height
System start--Network genesis time
Protocol parameters2Live protocol parameters
Proposed PP updates4Proposed parameter updates (empty in Conway)
Stake distribution5Pool stake and pledge
UTxO by address6UTxO set filtered by address
Stake address info10Delegation and rewards
Genesis config11System start, epoch length, slot length, security param
UTxO by TxIn15UTxO set filtered by transaction inputs
Stake pools16Set of registered pool key hashes
Pool parameters17Registered pool parameters
Pool state19Pool state (same as pool parameters)
Stake snapshots20Mark/set/go snapshots
Pool distribution21Pool stake distribution with VRF keys
Stake deleg deposits22Deposit amounts per registered stake credential
Constitution23Constitution anchor and guardrail script
Governance state24Active proposals, committee, and voting state
DRep state25Registered DReps with delegators
DRep stake distr26Total delegated stake per DRep
Committee state27Constitutional committee members
Vote delegatees28Vote delegation map per credential
Account state29Treasury and reserves
Non-myopic rewards6*Estimated rewards per pool for given stake amounts

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

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

Peer Manager

The peer manager classifies peers into three categories following the cardano-node model:

  • Cold -- Known but not connected
  • Warm -- Connected but not actively syncing
  • Hot -- Actively syncing (ChainSync + BlockFetch)

Peer Lifecycle

Cold --> Warm (TCP connection established)
Warm --> Hot  (Mini-protocols activated)
Hot  --> Warm (Demotion for poor performance)
Warm --> Cold (Disconnection)

Failure Handling

  • Exponential backoff on connection failures
  • Latency-based ranking and reputation scoring (EWMA metrics)
  • Adaptive peer selection preferring low-latency, reliable peers

Inbound Connections

  • Per-IP token bucket rate limiting for DoS protection
  • N2N server handles handshake, ChainSync, BlockFetch, KeepAlive, and TxSubmission2
  • Bidirectional diffusion mode supports both initiator and responder roles

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 (enabled after useLedgerAfterSlot)

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.

Protocol Parameters Reference

Cardano protocol parameters control the behavior of the network, including fees, block sizes, staking mechanics, and governance. These parameters can be queried from a running node and updated through governance actions.

Querying Parameters

torsten-cli query protocol-parameters \
  --socket-path ./node.sock \
  --out-file protocol-params.json

Fee Parameters

ParameterJSON KeyDescriptionMainnet Default
Min fee coefficienttxFeePerByte / minFeeAFee per byte of transaction size44
Min fee constanttxFeeFixed / minFeeBFixed fee component155381
Min UTxO value per byteutxoCostPerByte / adaPerUtxoByteMinimum lovelace per byte of UTxO4310

The transaction fee formula is:

fee = txFeePerByte * tx_size_in_bytes + txFeeFixed

Block Size Parameters

ParameterJSON KeyDescriptionMainnet Default
Max block body sizemaxBlockBodySizeMaximum block body size in bytes90112
Max transaction sizemaxTxSizeMaximum transaction size in bytes16384
Max block header sizemaxBlockHeaderSizeMaximum block header size in bytes1100

Staking Parameters

ParameterJSON KeyDescriptionMainnet Default
Stake address depositstakeAddressDeposit / keyDepositDeposit for stake key registration (lovelace)2000000
Pool depositstakePoolDeposit / poolDepositDeposit for pool registration (lovelace)500000000
Pool retire max epochpoolRetireMaxEpoch / eMaxMaximum future epochs for pool retirement18
Pool target countstakePoolTargetNum / nOptTarget number of pools (k parameter)500
Min pool costminPoolCostMinimum fixed pool cost (lovelace)170000000

Monetary Policy

ParameterDescription
Monetary expansion (rho)Rate of new ADA creation from reserves per epoch
Treasury cut (tau)Fraction of rewards directed to the treasury
Pledge influence (a0)How pledge affects reward calculations

Plutus Execution Parameters

ParameterJSON KeyDescriptionMainnet Default
Max tx execution unitsmaxTxExecutionUnits{memory, steps} per transaction{14000000, 10000000000}
Max block execution unitsmaxBlockExecutionUnits{memory, steps} per block{62000000, 40000000000}
Max value sizemaxValueSizeMaximum serialized value size in bytes5000
Collateral percentagecollateralPercentageCollateral % of total tx fee for Plutus txs150
Max collateral inputsmaxCollateralInputsMaximum collateral inputs per tx3

Governance Parameters (Conway)

ParameterJSON KeyDescriptionMainnet Default
DRep depositdrepDepositDeposit for DRep registration (lovelace)500000000
Gov action depositgovActionDepositDeposit for governance action submission (lovelace)100000000000
Gov action lifetimegovActionLifetimeGovernance action expiry (epochs)6

Voting Thresholds

Different governance action types require different voting thresholds from DReps, SPOs, and the Constitutional Committee:

Action TypeDRep ThresholdSPO ThresholdCC Threshold
No ConfidencedvtMotionNoConfidencepvtMotionNoConfidenceRequired
Update Committee (normal)dvtCommitteeNormalpvtCommitteeNormalN/A
Update Committee (no confidence)dvtCommitteeNoConfidencepvtCommitteeNoConfidenceN/A
New ConstitutiondvtUpdateToConstitutionN/ARequired
Hard Fork InitiationdvtHardForkInitiationpvtHardForkInitiationRequired
Protocol Parameter Update (network)dvtPPNetworkGroupN/ARequired
Protocol Parameter Update (economic)dvtPPEconomicGrouppvtPPEconomicGroupRequired
Protocol Parameter Update (technical)dvtPPTechnicalGroupN/ARequired
Protocol Parameter Update (governance)dvtPPGovGroupN/ARequired
Treasury WithdrawaldvtTreasuryWithdrawalN/ARequired

CBOR Field Numbers

When encoding protocol parameter updates in governance actions, each parameter maps to a CBOR field number:

CBOR KeyParameter
0txFeePerByte / minFeeA
1txFeeFixed / minFeeB
2maxBlockBodySize
3maxTxSize
4maxBlockHeaderSize
5stakeAddressDeposit / keyDeposit
6stakePoolDeposit / poolDeposit
7poolRetireMaxEpoch / eMax
8stakePoolTargetNum / nOpt
16minPoolCost
17utxoCostPerByte / adaPerUtxoByte
20maxTxExecutionUnits
21maxBlockExecutionUnits
22maxValueSize
23collateralPercentage
24maxCollateralInputs
30drepDeposit
31govActionDeposit
32govActionLifetime

Troubleshooting

Common issues and their solutions when running Torsten.

Build Issues

libclang not found

Error:

error: failed to run custom build command for `rocksdb-sys`
...
couldn't find any valid shared or static libraries named clang

Solution: Install the libclang development package for your OS:

# Ubuntu / Debian
sudo apt-get install -y libclang-dev

# macOS
brew install llvm

# Fedora
sudo dnf install clang-devel

# Arch Linux
sudo pacman -S clang

On macOS, you may also need to set:

export LIBCLANG_PATH="$(brew --prefix llvm)/lib"

Compilation is slow

The initial build compiles RocksDB 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:

  1. Firewall blocking outbound connections on port 3001. Ensure outbound TCP connections to port 3001 are allowed.

  2. Incorrect network magic. Verify the NetworkMagic in your config matches the target network:

    • Mainnet: 764824073
    • Preview: 2
    • Preprod: 1
  3. DNS resolution failure. If topology uses hostnames, ensure DNS is working:

    nslookup preview-node.play.dev.cardano.org
    
  4. Stale topology. Peer addresses may change. Download the latest topology from the Cardano Operations Book.

Handshake failures

Error: Handshake failed: version mismatch

This usually means the peer does not support the protocol version Torsten is requesting (V14+). Ensure you are connecting to an up-to-date cardano-node (version 10.x+).

Socket Issues

Cannot connect to node socket

Error: Cannot connect to node socket './node.sock': No such file or directory

Solutions:

  1. Node is not running. Start the node first.

  2. Wrong socket path. Verify the socket path matches what the node was started with:

    torsten-cli query tip --socket-path /path/to/actual/node.sock
    
  3. Permission denied. Ensure the user running the CLI has read/write access to the socket file.

  4. Stale socket file. If the node crashed, the socket file may remain. Delete it and restart:

    rm ./node.sock
    torsten-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.

Storage Issues

Database corruption

Symptoms: Node crashes on startup with RocksDB errors.

Solution: The safest approach is to delete the database and resync:

rm -rf ./db-path
torsten-node run ...

For faster recovery, use Mithril snapshot import:

rm -rf ./db-path
torsten-node mithril-import --network-magic 2 --database-path ./db-path
torsten-node run ...

Disk space

Cardano databases grow continuously. Approximate sizes:

NetworkDatabase Size
Mainnet90-140+ GB
Preview8-15+ GB
Preprod20-35+ GB

Monitor disk usage and ensure adequate free space.

Sync Issues

Sync is slow

Possible causes:

  1. Single peer. Torsten benefits from multiple peers for block fetching. Ensure your topology includes multiple bootstrap peers or enable ledger-based peer discovery.

  2. Network latency. The ChainSync protocol has an inherent per-header RTT (~300ms). High-latency connections will reduce throughput.

  3. Slow disk. RocksDB performance depends on disk I/O speed. SSDs are strongly recommended.

  4. 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:

  1. Peer disconnected. The node will reconnect automatically with exponential backoff. Wait a few minutes.

  2. 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.

  3. Resource exhaustion. Check for out-of-memory or file descriptor limits.

Memory Issues

Out of memory

Torsten'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 torsten-node run ...

# Debug specific crate
RUST_LOG=torsten_network=debug torsten-node run ...

# Trace level (very verbose)
RUST_LOG=trace torsten-node run ...

Log to file

Redirect stderr to a file:

torsten-node run ... 2>&1 | tee node.log

SIGHUP Topology Reload

To update topology without restarting:

# Edit topology.json
kill -HUP $(pidof torsten-node)

The node will log that the topology was reloaded and update the peer manager with the new configuration.

Getting Help

If you encounter an issue not covered here:

  1. Check the GitHub issues
  2. Open a new issue with:
    • Torsten version (torsten-node --version)
    • Operating system
    • Configuration files (redact any sensitive info)
    • Relevant log output
    • Steps to reproduce