Introduction
Torsten is a Cardano node implementation written in Rust, aiming for 100% compatibility with cardano-node (Haskell).
Built by Sandstone Pool.
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/:
| Binary | Description |
|---|---|
torsten-node | The Cardano node |
torsten-cli | The 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:
- Load the configuration and topology
- Connect to the preview testnet bootstrap peers
- Perform the N2N handshake (protocol version V14+)
- 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 -- Detailed configuration options
- Networks -- Connecting to mainnet, preview, or preprod
- Monitoring -- Prometheus metrics endpoint
- CLI Reference -- Full CLI command reference
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
| Field | Type | Default | Description |
|---|---|---|---|
Network | string | "Mainnet" | Network identifier: "Mainnet" or "Testnet" |
NetworkMagic | integer | auto | Network magic number. If omitted, derived from Network (764824073 for mainnet) |
EnableP2P | boolean | true | Enable P2P networking mode |
Protocol
| Field | Type | Default | Description |
|---|---|---|---|
Protocol.RequiresNetworkMagic | string | "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.
| Field | Type | Default | Description |
|---|---|---|---|
ShelleyGenesisFile | string | none | Path to Shelley genesis JSON |
ByronGenesisFile | string | none | Path to Byron genesis JSON |
AlonzoGenesisFile | string | none | Path to Alonzo genesis JSON |
ConwayGenesisFile | string | none | Path to Conway genesis JSON |
Tip: Genesis files for each network can be downloaded from the Cardano Operations Book.
P2P Parameters
| Field | Type | Default | Description |
|---|---|---|---|
TargetNumberOfActivePeers | integer | 20 | Target number of active (hot) peers |
TargetNumberOfEstablishedPeers | integer | 40 | Target number of established (warm) peers |
TargetNumberOfKnownPeers | integer | 100 | Target number of known (cold) peers |
Tracing
| Field | Type | Default | Description |
|---|---|---|---|
MinSeverity | string | "Info" | Minimum log severity level |
TraceOptions.TraceBlockFetchClient | boolean | false | Trace block fetch client activity |
TraceOptions.TraceBlockFetchServer | boolean | false | Trace block fetch server activity |
TraceOptions.TraceChainDb | boolean | false | Trace ChainDB operations |
TraceOptions.TraceChainSyncClient | boolean | false | Trace chain sync client activity |
TraceOptions.TraceChainSyncServer | boolean | false | Trace chain sync server activity |
TraceOptions.TraceForge | boolean | false | Trace block forging |
TraceOptions.TraceMempool | boolean | false | Trace mempool activity |
Log Level Control
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"
}
]
| Field | Type | Default | Description |
|---|---|---|---|
accessPoints | array | required | List of {address, port} entries |
advertise | boolean | false | Whether to share these peers via peer sharing protocol |
valency | integer | 1 | Deprecated. Target number of active connections. Use hotValency instead |
hotValency | integer | valency | Target number of hot (actively syncing) peers |
warmValency | integer | hotValency+1 | Target number of warm (connected, not syncing) peers |
trustable | boolean | false | Whether these peers are trusted for sync. Trusted peers are preferred during initial sync |
behindFirewall | boolean | false | If true, the node waits for inbound connections from these peers instead of connecting outbound |
diffusionMode | string | "InitiatorAndResponder" | Per-group diffusion mode. "InitiatorOnly" for unidirectional connections |
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
| Network | Magic | Description |
|---|---|---|
| Mainnet | 764824073 | The production Cardano network |
| Preview | 2 | Fast-moving testnet for early feature testing |
| Preprod | 1 | Stable testnet that mirrors mainnet behavior |
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:
- Preview: book.world.dev.cardano.org/environments/preview/
- Preprod: book.world.dev.cardano.org/environments/preprod/
- Mainnet: book.world.dev.cardano.org/environments/mainnet/
These include the full genesis files (Byron, Shelley, Alonzo, Conway) required for complete protocol parameter initialization.
Using the CLI with Different Networks
When querying a node connected to a testnet, pass the --testnet-magic flag to the CLI:
# Preview
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:
- Queries the Mithril aggregator for the latest available snapshot
- Downloads the snapshot archive (compressed with zstandard)
- Extracts the cardano-node chunk files
- Parses each block using the pallas CBOR decoder
- Bulk-imports blocks into Torsten's RocksDB-based ImmutableDB
Usage
torsten-node mithril-import \
--network-magic <magic> \
--database-path <path>
Arguments
| Argument | Default | Description |
|---|---|---|
--network-magic | 764824073 | Network magic (764824073=mainnet, 2=preview, 1=preprod) |
--database-path | db | Path to the database directory |
--temp-dir | system temp | Temporary directory for download and extraction |
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:
| Network | Aggregator URL |
|---|---|
| Mainnet | https://aggregator.release-mainnet.api.mithril.network/aggregator |
| Preview | https://aggregator.pre-release-preview.api.mithril.network/aggregator |
| Preprod | https://aggregator.release-preprod.api.mithril.network/aggregator |
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):
| Network | Compressed Archive | Extracted | Final DB |
|---|---|---|---|
| Mainnet | ~60-90 GB | ~120-180 GB | ~90-140 GB |
| Preview | ~5-10 GB | ~10-20 GB | ~8-15 GB |
| Preprod | ~15-25 GB | ~30-50 GB | ~20-35 GB |
The temporary directory needs enough space for both the compressed archive and the extracted files. After import, temporary files are automatically cleaned up.
Note: Ensure you have sufficient disk space before starting the import. The
--temp-dirflag can be used to direct temporary files to a different volume if needed.
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
| Metric | Type | Description |
|---|---|---|
torsten_blocks_received_total | counter | Total blocks received from peers |
torsten_blocks_applied_total | counter | Total blocks successfully applied to the ledger |
torsten_transactions_received_total | counter | Total transactions received |
torsten_transactions_validated_total | counter | Total transactions validated |
torsten_transactions_rejected_total | counter | Total transactions rejected |
torsten_peers_connected | gauge | Number of connected peers |
torsten_peers_cold | gauge | Number of cold (known but unconnected) peers |
torsten_peers_warm | gauge | Number of warm (connected, not syncing) peers |
torsten_peers_hot | gauge | Number of hot (actively syncing) peers |
torsten_sync_progress_percent | gauge | Chain sync progress (0-10000; divide by 100 for percentage) |
torsten_slot_number | gauge | Current slot number |
torsten_block_number | gauge | Current block number |
torsten_epoch_number | gauge | Current epoch number |
torsten_utxo_count | gauge | Number of entries in the UTxO set |
torsten_mempool_tx_count | gauge | Number of transactions in the mempool |
torsten_mempool_bytes | gauge | Size of the mempool in bytes |
torsten_rollback_count_total | counter | Total number of chain rollbacks |
torsten_blocks_forged_total | counter | Total blocks forged by this node |
torsten_delegation_count | gauge | Number of active stake delegations |
torsten_treasury_lovelace | gauge | Total 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:
- Slot leader check -- Each slot, the node uses its VRF key and the epoch nonce to determine if it is elected to produce a block.
- Block forging -- If elected, the node assembles a block from pending mempool transactions, signs it with the KES key, and includes the VRF proof.
- Block announcement -- The forged block is propagated to connected peers via the N2N protocol.
Required Keys
Cold Keys (Offline)
Cold keys identify the stake pool and should be kept offline (air-gapped) after initial setup.
Generate cold keys using the CLI:
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: -1disables ledger-based peer discovery. - Only local roots -- All connections are to your own relay nodes.
Leader Schedule
You can compute your pool's leader schedule 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:
-
Generate new KES keys:
torsten-cli node key-gen-KES \ --verification-key-file kes-new.vkey \ --signing-key-file kes-new.skey -
Issue a new operational certificate with the new KES key (on the air-gapped machine):
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 -
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
| Command | Description |
|---|---|
address | Address generation and manipulation |
key | Payment and stake key generation |
transaction | Transaction building, signing, and submission |
query | Node queries (tip, UTxO, protocol parameters, etc.) |
stake-address | Stake address registration, delegation, and vote delegation |
stake-pool | Stake pool operations (retirement certificates) |
governance | Conway governance (DRep, voting, proposals) |
node | Node 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 keystake.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
| Argument | Description |
|---|---|
--tx-in | Transaction input in tx_hash#index format. Can be specified multiple times |
--tx-out | Transaction output in address+lovelace format. Can be specified multiple times |
--change-address | Address to receive change |
--fee | Fee in lovelace (default: 200000) |
--ttl | Time-to-live slot number (optional) |
--certificate-file | Path to a certificate file to include (can be repeated) |
--withdrawal | Withdrawal in stake_address+lovelace format (can be repeated) |
--metadata-json-file | Path to a JSON metadata file (optional) |
--out-file | Output file for the transaction body |
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
| Value | Description |
|---|---|
yes | Vote in favor |
no | Vote against |
abstain | Abstain 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
| Crate | Description |
|---|---|
torsten-primitives | Core types: hashes, blocks, transactions, addresses, values, protocol parameters (Byron--Conway) |
torsten-crypto | Ed25519 keys, VRF, KES, text envelope format |
torsten-serialization | CBOR encoding/decoding for Cardano wire format via pallas |
torsten-network | Ouroboros mini-protocols (ChainSync, BlockFetch, TxSubmission, KeepAlive), N2N client/server, N2C server, multi-peer block fetch pool |
torsten-consensus | Ouroboros Praos, chain selection, epoch transitions, slot leader checks |
torsten-ledger | UTxO set, transaction validation, ledger state, certificate processing, native script evaluation, reward calculation |
torsten-mempool | Thread-safe transaction mempool |
torsten-storage | ChainDB (ImmutableDB via RocksDB with WriteBatch + VolatileDB in-memory) |
torsten-node | Main binary, config, topology, pipelined chain sync loop |
torsten-cli | cardano-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.hashis set during deserialization frompallas tx.hash()ChainSyncEvent::RollForwardusesBox<Block>to avoid large enum variant size- Invalid transactions (
is_valid: false) are skipped duringapply_block - Pool IDs are
Hash28(Blake2b-224), notHash32
Multi-Era Support
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:
- MsgFindIntersect -- Find a common point between the node and the peer
- MsgRequestNext -- Request the next header
- MsgRollForward -- Receive a new header
- MsgRollBackward -- Handle a chain reorganization
2. Block Fetch 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:
- MsgRequestRange -- Request a range of blocks by header hash
- MsgBlock -- Receive a block
- 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:
- Deserialization -- Raw CBOR bytes are decoded into Torsten's internal
Blocktype using pallas - Ledger validation -- Each block is validated against the current ledger state (UTxO checks, fee validation, certificate processing)
- Storage -- Valid blocks are added to the ChainDB (volatile database first, flushed to immutable when k-deep)
- 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:
- Identifies the rollback point (a slot/hash pair)
- Removes rolled-back blocks from the VolatileDB
- Reverts the ledger state to the rollback point
- Resumes header collection from the new tip
Only blocks in the VolatileDB (the last k=2160 blocks) can be rolled back. Blocks that have been flushed to the ImmutableDB are permanent.
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
- New blocks arrive from peers and are added to the VolatileDB
- Once a block is more than k slots deep (k=2160 for mainnet), it is flushed from the VolatileDB to the ImmutableDB
- Flushed blocks are removed from the VolatileDB
Block Queries
When querying for a block:
- The VolatileDB is checked first (fast, in-memory)
- 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:
- The ImmutableDB tip is read from RocksDB metadata
- The VolatileDB starts empty (in-memory state is lost on restart)
- 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
releaseprofile usesopt-level = 3andlto = "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:
- A VRF output -- A deterministic pseudo-random value
- A VRF proof -- A proof that the output was correctly computed
Leader Threshold
The VRF output is compared against a threshold derived from:
- The pool's relative stake (sigma)
- The active slot coefficient (f = 0.05 on mainnet)
The threshold is computed using the phi function:
phi(sigma) = 1 - (1 - f)^sigma
A slot leader is elected if VRF_output < phi(sigma).
Epoch Nonce
The epoch nonce is computed at each epoch boundary:
nonce = hash(rolling_nonce || first_block_hash_prev_epoch)
Where:
rolling_nonceis updated per-block:hash(prev_eta_v || hash(vrf_output))first_block_hash_prev_epochis 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:
- Chain comparison -- Compare the block height of competing chains
- Rollback support -- Roll back up to k=2160 blocks to switch to a longer chain
- Immutability -- Blocks deeper than k are considered final
Epoch Transitions
At each epoch boundary, 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:
- Go becomes the active snapshot
- Set moves to go
- Mark moves to set
- A new mark is taken from the current ledger state
Reward Calculation and Distribution
At each epoch boundary, rewards are calculated and distributed:
- Monetary expansion -- New ADA is created from the reserves based on the monetary expansion rate
- Fee collection -- Transaction fees from the epoch are collected
- Treasury cut -- A fraction (tau) of rewards goes to the treasury
- Pool rewards -- Remaining rewards are distributed to pools based on their performance
- Member distribution -- Pool rewards are split between the operator and delegators based on pool parameters (cost, margin, pledge)
Validation Checks
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:
- VRF key binding --
blake2b_256(header.vrf_vkey)must match the pool's registeredvrf_keyhash - VRF proof verification -- The VRF proof is cryptographically verified against the VRF public key
- Leader eligibility -- The VRF leader value is checked against the Praos threshold for the pool's relative stake using the phi function
Operational Certificate Verification
The operational certificate's Ed25519 signature is verified against the raw bytes signable format (matching Haskell's OCertSignable):
signable = hot_vkey(32 bytes) || counter(8 bytes BE) || kes_period(8 bytes BE)
signature = sign(cold_skey, signable)
The counter must be monotonically increasing per pool to prevent certificate replay.
KES Signature Verification
Block headers are signed using the Sum6Kes scheme (depth-6 binary sum composition over Ed25519). The KES key is evolved to the correct period offset from the operational certificate's start period. Verification checks:
- The KES signature over the header body bytes is valid
- The KES period matches the expected value for the block's slot
Slot Leader Eligibility
The VRF proof is checked to confirm the block producer was indeed elected for the slot, given the epoch nonce and their pool's stake.
Networking
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:
InitiatorOnlyorInitiatorAndResponder - Peer sharing flags
ChainSync
The ChainSync mini-protocol synchronizes block headers between peers:
- Client mode: Requests headers sequentially from a peer to track the chain
- Server mode: Serves headers to connected peers, with per-peer cursor tracking
Key messages:
MsgFindIntersect-- Find a common chain pointMsgRequestNext-- Request the next headerMsgRollForward-- Header deliveredMsgRollBackward-- Chain reorganizationMsgAwaitReply-- Peer has no new headers (at tip)
BlockFetch
The BlockFetch mini-protocol retrieves block bodies by hash:
- Client mode: Requests ranges of blocks from peers
- Server mode: Serves blocks to peers, validates block existence before serving
Key messages:
MsgRequestRange-- Request blocks in a slot rangeMsgBlock-- Block deliveredMsgNoBlocks-- Requested blocks not availableMsgBatchDone-- End of batch
TxSubmission2
The TxSubmission2 mini-protocol propagates transactions between peers:
- Bidirectional handshake (
MsgInit) - Flow-controlled transaction exchange with ack/req counts
- Inflight tracking per peer
- Mempool integration for serving transaction IDs and bodies
KeepAlive
The KeepAlive mini-protocol maintains connection liveness with periodic heartbeat messages.
PeerSharing
The PeerSharing mini-protocol enables gossip-based peer discovery. Peers exchange addresses of other known peers to help the network self-organize.
Node-to-Client (N2C) Protocol
N2C connections use Unix domain sockets and serve local clients (wallets, CLI tools). The N2C handshake supports versions V16-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:
| Query | Tag | Description |
|---|---|---|
| Chain tip | 0 | Current slot, hash, block number |
| Current epoch | 1 | Active epoch number |
| Current era | -- | Active era (Byron through Conway) |
| Block number | -- | Current chain height |
| System start | -- | Network genesis time |
| Protocol parameters | 2 | Live protocol parameters |
| Proposed PP updates | 4 | Proposed parameter updates (empty in Conway) |
| Stake distribution | 5 | Pool stake and pledge |
| UTxO by address | 6 | UTxO set filtered by address |
| Stake address info | 10 | Delegation and rewards |
| Genesis config | 11 | System start, epoch length, slot length, security param |
| UTxO by TxIn | 15 | UTxO set filtered by transaction inputs |
| Stake pools | 16 | Set of registered pool key hashes |
| Pool parameters | 17 | Registered pool parameters |
| Pool state | 19 | Pool state (same as pool parameters) |
| Stake snapshots | 20 | Mark/set/go snapshots |
| Pool distribution | 21 | Pool stake distribution with VRF keys |
| Stake deleg deposits | 22 | Deposit amounts per registered stake credential |
| Constitution | 23 | Constitution anchor and guardrail script |
| Governance state | 24 | Active proposals, committee, and voting state |
| DRep state | 25 | Registered DReps with delegators |
| DRep stake distr | 26 | Total delegated stake per DRep |
| Committee state | 27 | Constitutional committee members |
| Vote delegatees | 28 | Vote delegation map per credential |
| Account state | 29 | Treasury and reserves |
| Non-myopic rewards | 6* | Estimated rewards per pool for given stake amounts |
The query protocol uses an acquire/query/release pattern:
MsgAcquire-- Lock the ledger state at the current tipMsgQuery-- Execute queries against the locked stateMsgRelease-- Release the lock
LocalTxSubmission
Submits transactions from local clients to the node's mempool:
| Message | Description |
|---|---|
MsgSubmitTx | Submit a transaction (era ID + CBOR bytes) |
MsgAcceptTx | Transaction accepted into mempool |
MsgRejectTx | Transaction rejected with reason |
Submitted transactions undergo both Phase-1 (structural) and Phase-2 (Plutus script) validation before mempool admission.
LocalTxMonitor
Monitors the transaction mempool:
| Message | Description |
|---|---|
MsgAcquire | Acquire a mempool snapshot |
MsgHasTx | Check if a transaction is in the mempool |
MsgNextTx | Get the next transaction from the mempool |
MsgGetSizes | Get mempool capacity, size, and transaction count |
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:
- Topology file -- Bootstrap peers, local roots, and public roots
- PeerSharing protocol -- Gossip-based discovery from connected peers
- Ledger-based discovery -- SPO relay addresses extracted from pool registration certificates (enabled after
useLedgerAfterSlot)
Multiplexer
All mini-protocols run over a single TCP connection (N2N) or Unix socket (N2C), multiplexed by protocol ID:
| Protocol ID | Mini-Protocol |
|---|---|
| 0 | Handshake |
| 2 | ChainSync (N2N) |
| 3 | BlockFetch (N2N) |
| 4 | TxSubmission2 (N2N) |
| 8 | KeepAlive (N2N) |
| 10 | PeerSharing (N2N) |
| 5 | LocalChainSync (N2C) |
| 6 | LocalTxSubmission (N2C) |
| 7 | LocalStateQuery (N2C) |
| 9 | LocalTxMonitor (N2C) |
The multiplexer uses length-prefixed frames with protocol ID headers, matching the Ouroboros specification.
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
| Parameter | JSON Key | Description | Mainnet Default |
|---|---|---|---|
| Min fee coefficient | txFeePerByte / minFeeA | Fee per byte of transaction size | 44 |
| Min fee constant | txFeeFixed / minFeeB | Fixed fee component | 155381 |
| Min UTxO value per byte | utxoCostPerByte / adaPerUtxoByte | Minimum lovelace per byte of UTxO | 4310 |
The transaction fee formula is:
fee = txFeePerByte * tx_size_in_bytes + txFeeFixed
Block Size Parameters
| Parameter | JSON Key | Description | Mainnet Default |
|---|---|---|---|
| Max block body size | maxBlockBodySize | Maximum block body size in bytes | 90112 |
| Max transaction size | maxTxSize | Maximum transaction size in bytes | 16384 |
| Max block header size | maxBlockHeaderSize | Maximum block header size in bytes | 1100 |
Staking Parameters
| Parameter | JSON Key | Description | Mainnet Default |
|---|---|---|---|
| Stake address deposit | stakeAddressDeposit / keyDeposit | Deposit for stake key registration (lovelace) | 2000000 |
| Pool deposit | stakePoolDeposit / poolDeposit | Deposit for pool registration (lovelace) | 500000000 |
| Pool retire max epoch | poolRetireMaxEpoch / eMax | Maximum future epochs for pool retirement | 18 |
| Pool target count | stakePoolTargetNum / nOpt | Target number of pools (k parameter) | 500 |
| Min pool cost | minPoolCost | Minimum fixed pool cost (lovelace) | 170000000 |
Monetary Policy
| Parameter | Description |
|---|---|
| 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
| Parameter | JSON Key | Description | Mainnet Default |
|---|---|---|---|
| Max tx execution units | maxTxExecutionUnits | {memory, steps} per transaction | {14000000, 10000000000} |
| Max block execution units | maxBlockExecutionUnits | {memory, steps} per block | {62000000, 40000000000} |
| Max value size | maxValueSize | Maximum serialized value size in bytes | 5000 |
| Collateral percentage | collateralPercentage | Collateral % of total tx fee for Plutus txs | 150 |
| Max collateral inputs | maxCollateralInputs | Maximum collateral inputs per tx | 3 |
Governance Parameters (Conway)
| Parameter | JSON Key | Description | Mainnet Default |
|---|---|---|---|
| DRep deposit | drepDeposit | Deposit for DRep registration (lovelace) | 500000000 |
| Gov action deposit | govActionDeposit | Deposit for governance action submission (lovelace) | 100000000000 |
| Gov action lifetime | govActionLifetime | Governance action expiry (epochs) | 6 |
Voting Thresholds
Different governance action types require different voting thresholds from DReps, SPOs, and the Constitutional Committee:
| Action Type | DRep Threshold | SPO Threshold | CC Threshold |
|---|---|---|---|
| No Confidence | dvtMotionNoConfidence | pvtMotionNoConfidence | Required |
| Update Committee (normal) | dvtCommitteeNormal | pvtCommitteeNormal | N/A |
| Update Committee (no confidence) | dvtCommitteeNoConfidence | pvtCommitteeNoConfidence | N/A |
| New Constitution | dvtUpdateToConstitution | N/A | Required |
| Hard Fork Initiation | dvtHardForkInitiation | pvtHardForkInitiation | Required |
| Protocol Parameter Update (network) | dvtPPNetworkGroup | N/A | Required |
| Protocol Parameter Update (economic) | dvtPPEconomicGroup | pvtPPEconomicGroup | Required |
| Protocol Parameter Update (technical) | dvtPPTechnicalGroup | N/A | Required |
| Protocol Parameter Update (governance) | dvtPPGovGroup | N/A | Required |
| Treasury Withdrawal | dvtTreasuryWithdrawal | N/A | Required |
CBOR Field Numbers
When encoding protocol parameter updates in governance actions, each parameter maps to a CBOR field number:
| CBOR Key | Parameter |
|---|---|
| 0 | txFeePerByte / minFeeA |
| 1 | txFeeFixed / minFeeB |
| 2 | maxBlockBodySize |
| 3 | maxTxSize |
| 4 | maxBlockHeaderSize |
| 5 | stakeAddressDeposit / keyDeposit |
| 6 | stakePoolDeposit / poolDeposit |
| 7 | poolRetireMaxEpoch / eMax |
| 8 | stakePoolTargetNum / nOpt |
| 16 | minPoolCost |
| 17 | utxoCostPerByte / adaPerUtxoByte |
| 20 | maxTxExecutionUnits |
| 21 | maxBlockExecutionUnits |
| 22 | maxValueSize |
| 23 | collateralPercentage |
| 24 | maxCollateralInputs |
| 30 | drepDeposit |
| 31 | govActionDeposit |
| 32 | govActionLifetime |
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:
-
Firewall blocking outbound connections on port 3001. Ensure outbound TCP connections to port 3001 are allowed.
-
Incorrect network magic. Verify the
NetworkMagicin your config matches the target network:- Mainnet:
764824073 - Preview:
2 - Preprod:
1
- Mainnet:
-
DNS resolution failure. If topology uses hostnames, ensure DNS is working:
nslookup preview-node.play.dev.cardano.org -
Stale topology. Peer addresses may change. Download the latest topology from the Cardano Operations Book.
Handshake failures
Error: Handshake failed: version mismatch
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:
-
Node is not running. Start the node first.
-
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 -
Permission denied. Ensure the user running the CLI has read/write access to the socket file.
-
Stale socket file. If the node crashed, the socket file may remain. Delete it and restart:
rm ./node.sock 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:
| Network | Database Size |
|---|---|
| Mainnet | 90-140+ GB |
| Preview | 8-15+ GB |
| Preprod | 20-35+ GB |
Monitor disk usage and ensure adequate free space.
Sync Issues
Sync is slow
Possible causes:
-
Single peer. Torsten benefits from multiple peers for block fetching. Ensure your topology includes multiple bootstrap peers or enable ledger-based peer discovery.
-
Network latency. The ChainSync protocol has an inherent per-header RTT (~300ms). High-latency connections will reduce throughput.
-
Slow disk. RocksDB performance depends on disk I/O speed. SSDs are strongly recommended.
-
CPU-bound during ledger validation. Block processing includes UTxO validation and Plutus script execution. This is CPU-intensive during sync.
Recommendation: Use Mithril snapshot import to bypass the initial sync bottleneck entirely.
Sync stalls
Symptoms: Progress percentage stops increasing, no new blocks logged.
Possible causes:
-
Peer disconnected. The node will reconnect automatically with exponential backoff. Wait a few minutes.
-
All peers at same height. If all configured peers are also syncing, they may not have new blocks to serve. Add more peers to the topology.
-
Resource exhaustion. Check for out-of-memory or file descriptor limits.
Memory Issues
Out of memory
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:
- Check the GitHub issues
- Open a new issue with:
- Torsten version (
torsten-node --version) - Operating system
- Configuration files (redact any sensitive info)
- Relevant log output
- Steps to reproduce
- Torsten version (