Run Cardano Nodes

Run a Relay Node

Running a Cardano node is easy. But first, lets check whether your Docker environment is working.

$ docker run --rm centos echo "Hello Cardano"
Hello Cardano

In case you'd like to find out more about what we've just done, take a look at docker run.

In case you don't have Docker installed just yet, take a look at Installing Docker.

Great, this is working. Now lets start the Cardano relay node.

$ docker run --detach \
    --name=relay \
    -p 3001:3001 \
    -e CARDANO_UPDATE_TOPOLOGY=true \
    -v node-data:/opt/cardano/data \
    nessusio/cardano-node run    

This will run the Cardano node detached from the current terminal session, publish the default port 3001 on the host network and write the block data to a docker volume. Please make sure that this port is accessible for incoming connections. The above should run on Windows, MacOS, Linux and on x86_64 or arm64 just the same. Full details about this image are given here.

A quick check of our docker stats should show that this container is indeed running.

$ docker stats

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O          BLOCK I/O     PIDS
7789064845b5   relay     230.20%   195.7MiB / 7.63GiB   2.50%     15.1MB / 297kB   12.3kB / 0B   15

The above shows the output on a RaspberryPi 4, with aggregated CPU usage for the 4 ARM CPUs. The Pi is not very busy at the moment and when fully synced will even be less so.

You can look at the container's console output like this ...

$ docker logs -f relay

Running the cardano node ...
CARDANO_CONFIG=/opt/cardano/config/mainnet-config.json
CARDANO_TOPOLOGY=/opt/cardano/config/mainnet-topology.json
CARDANO_BIND_ADDR=0.0.0.0
CARDANO_PORT=3001
CARDANO_DATABASE_PATH=/opt/cardano/data
CARDANO_SOCKET_PATH=/opt/cardano/ipc/socket
CARDANO_LOG_DIR=/opt/cardano/logs
CARDANO_PUBLIC_IP=
CARDANO_CUSTOM_PEERS=
CARDANO_UPDATE_TOPOLOGY=true
CARDANO_BLOCK_PRODUCER=false
cardano-node run --config /opt/cardano/config/mainnet-config.json --topology /opt/cardano/config/mainnet-topology.json --database-path /opt/cardano/data --socket-path /opt/cardano/ipc/socket --host-addr 0.0.0.0 --port 3001
Topology update: 17 * * * * root topologyUpdate
Initially waiting for 10 minutes ...
Listening on http://127.0.0.1:12798
[399de2cf:cardano.node.networkMagic:Notice:5] [2021-03-02 19:07:08.28 UTC] NetworkMagic 764824073
[399de2cf:cardano.node.basicInfo.protocol:Notice:5] [2021-03-02 19:07:08.28 UTC] Byron; Shelley
[399de2cf:cardano.node.basicInfo.version:Notice:5] [2021-03-02 19:07:08.28 UTC] 1.25.1
...

After a little while, you should be seeing that the node is finding initial peers and starts syncing the block chain.

Stopping the Container

The Cardano node likes to do a graceful shutdown with some database housekeeping before the process terminates. This image has built-in SIGINT (Ctrl+C) redirection so that you can gracefully shut down the node like this ...

$ docker stop relay

Removing the Container

It is not a good idea to forcefully remove a running container with docker rm -f because this bypasses graceful shutdown and will cause the node to re-validate the entire blockchain. On a fully synchronized node this may take > 15min. Instead, you'd want to stop the container first and then do a regular remove like this ...

$ docker rm relay

Topology Updater

There is currently no active P2P module in cardano-1.25.x. Your node may call out to well known relay nodes, but you may never have incoming connections. According to this it is necessary to update your topology every hour. At the time of writing, the node doesn't do this on its own.

This functionality has been built into the image as well. The topology updater is triggered by CARDANO_UPDATE_TOPOLOGY=true, which will automatically call a topology update procedure once every hour.

After three hours, the network will have accepted you as a new peer. On consecutive hourly calls it will respond with ...

  1. nice to meet you

  2. welcome to the topology

  3. glad you're staying with us

You can look at the output of the topology updater like this ...

$ docker exec -it relay tail /opt/cardano/logs/topologyUpdateResult

{ "resultcode": "201", "datetime":"2021-01-10 18:30:06", "clientIp": "209.250.233.200", "iptype": 4, "msg": "nice to meet you" }
{ "resultcode": "203", "datetime":"2021-01-10 19:30:03", "clientIp": "209.250.233.200", "iptype": 4, "msg": "welcome to the topology" }
{ "resultcode": "204", "datetime":"2021-01-10 20:30:04", "clientIp": "209.250.233.200", "iptype": 4, "msg": "glad you're staying with us" }

Live View Monitoring

After looking at the console output for a while, you may wish to have all the pertinent information available on one screen. Perhaps even without running an additional heavy-weight monitoring process.

An excellent bash based monitor that runs once every few seconds is provided for us by guild-operators. The image incorporates this monitor and configures it automatically according to the node's config

You can start the monitoring process like this ...

$ docker exec -it relay gLiveView

For details on how to execute a process within a running container, take a look at docker exec.

Command Line Interface

We can also use the image to run Cardano CLI commands.

For this to work, the node must share its IPC socket location, which can then be use in the alias definition.

Define a cardano-cli alias

$ alias cardano-cli="docker run -it --rm \
  -v node-ipc:/opt/cardano/ipc \
  nessusio/cardano-node cardano-cli"

Run stateless CLI commands

$ cardano-cli query tip --mainnet
{
    "blockNo": 5418105,
    "headerHash": "ae822475da72052b58c7890bc24b191d31d8bd58d9ac7d1e407f7a2e16c3310d",
    "slotNo": 23299720
}

This approach is stateless because all output files that such a CLI process might generate, will be lost when the process terminates, which is when the command returns.

There are two possible solutions to this problem. First, we could exec into to running container and invoke cardano-cli from within the relay process like this ...

$ docker exec -it relay bash
root@411cff448c06:~# cardano-cli query tip --mainnet
{
    "blockNo": 5418105,
    "headerHash": "ae822475da72052b58c7890bc24b191d31d8bd58d9ac7d1e407f7a2e16c3310d",
    "slotNo": 23299720
}
root@411cff448c06:~# exit

All generated output files would live in the relay node and also share its lifecycle i.e. get removed when the container gets removed.

Run stateful CLI commands

A better approach is perhaps to mount some local directory into the container like this ...

$ alias cardano-cli="docker run -it --rm \
  -v ~/cardano:/var/cardano/local \
  -v relay-ipc:/opt/cardano/ipc \
  nessusio/cardano-node cardano-cli"

and then ...

$ cardano-cli query protocol-parameters \
    --out-file /var/cardano/local/protocol.json \
    --mary-era \
    --mainnet

$ cat ~/cardano/protocol.json | grep decent
    "decentralisationParam": 0.12

Custom Configuration

In this section we define a custom config volume called cardano-relay-config. It holds the mainnet-topology.json that we define according to our needs. Note, that our custom config lives in /var/cardano/config and not in the default location /opt/cardano/config.

Define the Relay Topology

The Relay connects to the World and the Block Producer

$ cat << EOF > ~/cardano/config/mainnet-relay-topology.json
{
  "Producers": [
    {
      "addr": "relays-new.cardano-mainnet.iohk.io",
      "port": 3001,
      "valency": 1
    },
    {
      "addr": "bprod.ip.or.dns.name",
      "port": 3001,
      "valency": 1
    }
  ]
}
EOF

Setup the config volume

We now copy the topology file that we generated above to the cardano-relay-config volume.

$ docker run --name=tmp -v cardano-relay-config:/var/cardano/config centos
$ docker cp ~/cardano/config/mainnet-relay-topology.json tmp:/var/cardano/config/mainnet-topology.json
$ docker rm -f tmp

Start the Relay Node

We can now start the relay again with that custom config volume in place. Of course, we could have mounted the topology file into the container directly, but the volume approach is much to be preferred especially when you consider multiple config/key files in such a volume.

$ docker run --detach \
    --name=relay \
    --restart=always \
    -p 3001:3001 \
    -e CARDANO_UPDATE_TOPOLOGY=true \
    -e CARDANO_PUBLIC_IP="relay01.astorpool.net" \
    -e CARDANO_CUSTOM_PEERS="bprod.ip.or.dns.name:3001" \
    -e CARDANO_TOPOLOGY="/var/cardano/config/mainnet-topology.json" \
    -v cardano-relay-config:/var/cardano/config  \
    -v /mnt/disks/data00:/opt/cardano/data \
    nessusio/cardano-node run

Running a Block Producer

The block producer connects to one or more trusted relays and no other peers.

$ cat << EOF > cardano/config/mainnet-bprod-topology.json
{
  "Producers": [
    {
      "addr": "relay.ip.or.dns.name",
      "port": 3001,
      "valency": 1
    }
  ]
}
EOF

Setup the config volume

Similar to above, we now copy the topology to the cardano-prod-config volume. Additionally, we copy keys and certificates to that volume so that we can then reference it from the container configuration.

$ docker run --name=tmp -v cardano-bprod-config:/var/cardano/config centos
$ docker cp ~/cardano/config/mainnet-bprod-topology.json tmp:/var/cardano/config/mainnet-topology.json
$ docker cp ~/cardano/keys tmp:/var/cardano/config/keys
$ docker rm -f tmp

Start the Block Producer Node

After having done all the required steps to generate the pool keys, we can now start the block producer like this ...

$ docker run --detach \
    --name=prod \
    --restart=always \
    -e CARDANO_BLOCK_PRODUCER=true \
    -e CARDANO_TOPOLOGY="/var/cardano/config/mainnet-topology.json" \
    -e CARDANO_SHELLEY_KES_KEY="/var/cardano/config/keys/kes.skey" \
    -e CARDANO_SHELLEY_VRF_KEY="/var/cardano/config/keys/vrf.skey" \
    -e CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE="/var/cardano/config/keys/node.cert" \
    -v cardano-prod-config:/var/cardano/config  \
    -v /mnt/disks/data01:/opt/cardano/data \
    nessusio/cardano-node run

The block producer is meant to run on a different host than the relay. We do this for security reasons and also for fail safety. The block producer could connect to more than one redundant relays. If one of them goes down, the block producer would continue to receive transactions and create blocks when scheduled.

Leaderlog Schedule

For a Stake Pool Operator it is important to know when the node is scheduled to produce the next block. We definitely want to be online at that important moment and fulfill our block producing duties. There are better times to do node maintenance.

This important functionality has also been built into nessusio/cardano-tools the image.

First, we also define an alias and ping the node that we want to work with.

Details about this API are here.

$ alias cncli="docker run -it --rm \
  -v ~/cardano/keys:/var/cardano/keys \
  -v cncli:/var/cardano/cncli \
  nessusio/cardano-tools cncli"
  
NODE_IP=10.128.0.31

cncli ping --host $NODE_IP
{
  "status": "ok",
  "host": "10.128.0.31",
  "port": 3001,
  "connectDurationMs": 0,
  "durationMs": 53
}

Syncing the database

This command connects to a remote node and synchronizes blocks to a local sqlite database.

$ cncli sync --host $NODE_IP \
  --db /var/cardano/cncli/cncli.db \
  --no-service

...
2021-03-04T10:23:19.719Z INFO  cardano_ouroboros_network::protocols::chainsync   > block 5417518 of 5417518, 100.00% synced
2021-03-04T10:23:23.459Z INFO  cncli::nodeclient::sync                           > Exiting...

Running leaderlog

We can now obtain the leader schedule for our pool.

$ cncli leaderlog \
  --pool-id 9e8009b249142d80144dfb681984e08d96d51c2085e8bb6d9d1831d2 \
  --shelley-genesis /opt/cardano/config/mainnet-shelley-genesis.json \
  --byron-genesis /opt/cardano/config/mainnet-byron-genesis.json \
  --pool-vrf-skey /var/cardano/keys/pool/vrf.skey \
  --db /var/cardano/cncli/cncli.db \
  --tz Europe/Berlin \
  --ledger-set current | tee leaderlog.json
  
cat leaderlog.json | jq -c ".assignedSlots[] | {no: .no, slot: .slotInEpoch, at: .at}"

{"no":1,"slot":165351,"at":"2021-02-26T20:40:42+01:00"}
{"no":2,"slot":312656,"at":"2021-02-28T13:35:47+01:00"}
{"no":3,"slot":330588,"at":"2021-02-28T18:34:39+01:00"}
{"no":4,"slot":401912,"at":"2021-03-01T14:23:23+01:00"}

Last updated