Setting up cardano-node with mithril-client on preview, preprod, and mainnet

My goal with this post is to have cardano-nodes on both testnets – preview and preprod – and on mainnet running as a user, so I can later use them for minting, contract experiments etc. I’m not going to run a pool and this is not about setting up one.

To setup as fast and simple as possible, I’m going to use the pre-compiled binaries. Moreover, since we now have Mithril to get snapshots of the chains and do not have to synchronise from the beginning anymore, I’m going to use that.

Installing the software

I’m downloading the latest stable versions of cardano-node (8.7.3 at the time of last update) from https://github.com/input-output-hk/cardano-node/releases and of mithril (v2347.0 at the time of last update) from https://github.com/input-output-hk/mithril/releases. Then, I unpack those archives and put the binaries in there in the PATH.

I won’t give copy and paste instructions here. There are far too many already out there. A Cardano forum is not the right place to give a thorough introduction to Linux, bash, etc. You should learn a bit about that from the plethora of resources for Linux in general and your specific Linux distribution that are readily available out there.

After that, you can choose how you would like to organise your (Cardano) software. Personally, I chose to have the different packages in ~/.local/cardano/software/:

$ ls -l ~/.local/cardano/software/
drwxr-xr-x 1 sean sean       308 2023-12-15 17:40 cardano-node-8.7.3/
-rw-r--r-- 1 sean sean 114627802 2024-01-15 14:30 cardano-node-8.7.3-linux.tar.gz
drwxr-xr-x 1 sean sean       414 2024-01-04 09:10 mithril-2347.0/
-rw-r--r-- 1 sean sean  61720079 2024-01-04 09:08 mithril-2347.0-linux-x64.tar.gz

Observe:

  • IOG tends to pack its binary distributions such that they unpack in the current directory. So, I manually created those versioned directories, changed into them and called tar xaf from in there.
  • The binaries in the mithril distribution were not set to executable. I had to manually chmod +x them.

And I then have symbolic links in ~/.local/bin/ which for me is in the PATH anyway:

$ ls -l ~/.local/bin/
lrwxrwxrwx 1 sean sean 45 2024-01-15 14:33 bech32 -> ../cardano/software/cardano-node-8.7.3/bech32*
lrwxrwxrwx 1 sean sean 50 2024-01-15 14:33 cardano-cli -> ../cardano/software/cardano-node-8.7.3/cardano-cli*
lrwxrwxrwx 1 sean sean 51 2024-01-15 14:33 cardano-node -> ../cardano/software/cardano-node-8.7.3/cardano-node*
lrwxrwxrwx 1 sean sean 49 2024-01-04 09:15 mithril-client -> ../cardano/software/mithril-2347.0/mithril-client*

That way, I can easily switch to new versions by changing the symbolic links, with the possibility to easily switch back as long as I have not deleted the old version.

Using Mithril to get snapshots

Mithril can be used to bootstrap nodes on mainnet as well as both testnets as described in https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node.

I decided to put the chains next to each other in ~/.local/cardano/chains/. So, I’m doing in ~/.local/cardano/chains/mainnet/:

$ export NETWORK=mainnet
$ export AGGREGATOR_ENDPOINT=https://aggregator.release-mainnet.api.mithril.network/aggregator
$ export GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/release-mainnet/genesis.vkey)
$ mithril-client snapshot list
$ mithril-client snapshot download <latest digest>

In ~/.local/cardano/chains/preprod/:

$ export NETWORK=preprod
$ export AGGREGATOR_ENDPOINT=https://aggregator.release-preprod.api.mithril.network/aggregator
$ export GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/release-preprod/genesis.vkey)
$ mithril-client snapshot list
$ mithril-client snapshot download <latest digest>

And in ~/.local/cardano/chains/preview/:

$ export NETWORK=preview
$ export AGGREGATOR_ENDPOINT=https://aggregator.pre-release-preview.api.mithril.network/aggregator
$ export GENESIS_VERIFICATION_KEY=$(curl -s https://raw.githubusercontent.com/input-output-hk/mithril/main/mithril-infra/configuration/pre-release-preview/genesis.vkey)
$ mithril-client snapshot list
$ mithril-client snapshot download <latest digest>

mithril-client downloads the databases into db/ directories in the current directory and, when the downloads are ready, verifies the signatures (after all that is what Mithril is all about).

Download and check of preprod was finished in less than six minutes, of preview in less than eight minutes, and of mainnet in 74 minutes:

Getting and adapting the configurations

The configuration and Genesis files are obtained from https://book.world.dev.cardano.org/environments.html.

In ~/.local/cardano/chains/mainnet/config/:

$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/config.json
$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/topology.json
$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/byron-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/shelley-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/alonzo-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/mainnet/conway-genesis.json

In ~/.local/cardano/chains/preprod/config/:

$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/config.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/topology.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/byron-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/shelley-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/alonzo-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preprod/conway-genesis.json

In ~/.local/cardano/chains/preview/config/:

$ curl -s -O https://book.world.dev.cardano.org/environments/preview/config.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preview/topology.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preview/byron-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preview/shelley-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preview/alonzo-genesis.json
$ curl -s -O https://book.world.dev.cardano.org/environments/preview/conway-genesis.json

I decided that I need neither EKG nor Prometheus for my use case (local node just to submit transactions and experiment around with). The logs in journalctl are more than enough. So, I removed superfluous configuration in all three configs:

$ diff -u1 config-original.json config.json
--- config-original.json	2024-01-04 10:25:32.232620424 +0100
+++ config.json	2024-01-04 10:26:16.619097477 +0100
@@ -63,3 +63,3 @@
   "TracingVerbosity": "NormalVerbosity",
-  "TurnOnLogMetrics": true,
+  "TurnOnLogMetrics": false,
   "TurnOnLogging": true,
@@ -74,27 +74,4 @@
   ],
-  "hasEKG": 12788,
-  "hasPrometheus": [
-    "127.0.0.1",
-    12798
-  ],
   "minSeverity": "Info",
   "options": {
-    "mapBackends": {
-      "cardano.node.metrics": [
-        "EKGViewBK"
-      ],
-      "cardano.node.resources": [
-        "EKGViewBK"
-      ]
-    },
-    "mapSubtrace": {
-      "cardano.node.metrics": {
-        "subtrace": "Neutral"
-      }
-    }
-  },
-  "rotation": {
-    "rpKeepFilesNum": 10,
-    "rpLogLimitBytes": 5000000,
-    "rpMaxAgeHours": 24
   },

Since I want to be able to run the nodes on the different chains/networks in parallel and the port the node itself listens on will be given on the command line and is not set in config.json, I create environment files that can be read by the systemd unit in the next section:

$ cat ~/.local/cardano/chains/mainnet/env
CARDANO_NODE_PORT=3001
$ cat ~/.local/cardano/chains/preprod/env
CARDANO_NODE_PORT=3002
$ cat ~/.local/cardano/chains/preview/env
CARDANO_NODE_PORT=3003

Creating a systemd user unit

I want to be able to manage the nodes with systemctl, but not as system-wide units (as in the usual setups according to CoinCashew or CNTools), but as user units.

I create the following unit file:

$ cat ~/.config/systemd/user/cnode@.service
[Unit]
Description=Cardano Node
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/.local/cardano/chains/%i/
EnvironmentFile=%h/.local/cardano/chains/%i/env
ExecStart=%h/.local/bin/cardano-node run --topology config/topology.json --database-path db --socket-path socket --config config/config.json --port ${CARDANO_NODE_PORT}
SyslogIdentifier=cnode@%i
KillSignal=SIGINT
TimeoutStopSec=300
LimitNOFILE=32768
Restart=always
RestartSec=5

[Install]
WantedBy=default.target

That the name of the unit ends in an @ means that it is started with an instance name that is available inside the unit as %i. That way, I can use this same unit file to manage all three networks/chains with it.

Changing into the directory for the corresponding chain makes the call to cardano-node very simple and there are no further scripts needed.

Starting all three chains:

$ systemctl --user start cnode@mainnet
$ systemctl --user start cnode@preprod
$ systemctl --user start cnode@preview

This also organises them into a slice that can be used to query the state of all of them at once:

$ systemctl --user status app-cnode.slice
● app-cnode.slice - Slice /app/cnode
     Loaded: loaded
     Active: active since Mon 2024-01-15 14:37:15 CET; 8h ago
      Tasks: 48
     Memory: 24.6G (peak: 25.7G swap: 1.5G swap peak: 1.5G zswap: 830.1M)
        CPU: 8h 15min 53.907s
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/app-cnode.slice
             ├─cnode@mainnet.service
             │ └─72665 /home/sean/.local/bin/cardano-node run --topology config/topology.json --database>
             ├─cnode@preprod.service
             │ └─72688 /home/sean/.local/bin/cardano-node run --topology config/topology.json --database>
             └─cnode@preview.service
               └─72710 /home/sean/.local/bin/cardano-node run --topology config/topology.json --database>

Jan 15 22:49:30 nat cnode@preview[72710]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:49:30.32 U>
Jan 15 22:49:36 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:49:36.22 U>
Jan 15 22:49:38 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:49:38.14 U>
Jan 15 22:49:56 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:49:56.52 U>
Jan 15 22:50:12 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:50:12.09 U>
Jan 15 22:50:19 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Notice:22] [2024-01-15 21:50:19.36 U>
Jan 15 22:50:20 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:50:20.14 U>
Jan 15 22:50:21 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Notice:22] [2024-01-15 21:50:21.87 U>
Jan 15 22:50:27 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:50:27.17 U>
Jan 15 22:50:27 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Notice:22] [2024-01-15 21:50:27.84 U>

$ journalctl --user -fu app-cnode.slice
Jan 15 22:50:54 nat cnode@preview[72710]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:50:54.45 UTC] Chain extended, new tip: e02ce08ebe0b38e929f3850ce49a38a3e3f84abe466cee401d3a98bc8e5208f3 at slot 38699454
Jan 15 22:51:06 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Notice:22] [2024-01-15 21:51:06.56 UTC] Chain extended, new tip: 141eb03ad77650bb9af77f191e4f3f537762aa9a1917defa316955df8d5695c8 at slot 113789175
Jan 15 22:51:17 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Notice:22] [2024-01-15 21:51:17.20 UTC] Chain extended, new tip: ee3e9f6afe03bb79f518f0a2de1b2dad55c0d5b45f351d270300f623ad552f1a at slot 113789185
Jan 15 22:51:19 nat cnode@preprod[72688]: [nat:cardano.node.ChainDB:Notice:20] [2024-01-15 21:51:19.17 UTC] Chain extended, new tip: b83c3c1c9f823224688b4d02827266eb93b5d27a4ddd0c49b278ac905addee31 at slot 49672279
^C

Setting bash aliases

Finally, I created some aliases for bash and put them in one of my startup files to be able to use all three nodes with cardano-cli:

$ alias | grep cardano-cli
alias cardano-cli-mainnet='CARDANO_NODE_SOCKET_PATH=/home/sean/Cardano/Chains/mainnet/socket cardano-cli'
alias cardano-cli-preprod='CARDANO_NODE_SOCKET_PATH=/home/sean/Cardano/Chains/preprod/socket cardano-cli'
alias cardano-cli-preview='CARDANO_NODE_SOCKET_PATH=/home/sean/Cardano/Chains/preview/socket cardano-cli'
$ cardano-cli-mainnet query tip --mainnet
{
    "block": 9152619,
    "epoch": 429,
    "era": "Babbage",
    "hash": "09795edf1b299b5413923bef8d994aca43935e96befe73252c03d81fb8ce347f",
    "slot": 100323679,
    "slotInEpoch": 358879,
    "slotsToEpochEnd": 73121,
    "syncProgress": "100.00"
}
$ cardano-cli-preprod query tip --testnet-magic 1
{
    "block": 1264577,
    "epoch": 87,
    "era": "Babbage",
    "hash": "a8584e677d0ba6827965aef55cdf08d7bf279cac798a151cad790ace51132191",
    "slot": 36206790,
    "slotInEpoch": 264390,
    "slotsToEpochEnd": 167610,
    "syncProgress": "100.00"
}
$ cardano-cli-preview query tip --testnet-magic 2
{
    "block": 1105286,
    "epoch": 292,
    "era": "Babbage",
    "hash": "9b00d4d23cee047dc24266bea6eb591b8c1a87d2c1b7b64f7a8436813365e1d6",
    "slot": 25233954,
    "slotInEpoch": 5154,
    "slotsToEpochEnd": 81246,
    "syncProgress": "100.00"
}

And this concludes my journey in setting up nodes on mainnet and both testnets in parallel under a user account using pre-compiled binaries and Mithril to reduce effort and time as much as possible.

To summarise, these are the files and directories, we now have for the three chains/networks:

$ tree -L 3 ~/.local/cardano/chains/
/home/sean/.local/cardano/chains/
├── mainnet
│   ├── config
│   │   ├── alonzo-genesis.json
│   │   ├── byron-genesis.json
│   │   ├── config-original.json
│   │   ├── config.json
│   │   ├── conway-genesis.json
│   │   ├── shelley-genesis.json
│   │   └── topology.json
│   ├── db
│   │   ├── immutable
│   │   ├── ledger
│   │   ├── lock
│   │   ├── protocolMagicId
│   │   └── volatile
│   ├── env
│   └── socket
├── preprod
│   ├── config
│   │   ├── alonzo-genesis.json
│   │   ├── byron-genesis.json
│   │   ├── config-original.json
│   │   ├── config.json
│   │   ├── conway-genesis.json
│   │   ├── shelley-genesis.json
│   │   └── topology.json
│   ├── db
│   │   ├── immutable
│   │   ├── ledger
│   │   ├── lock
│   │   ├── protocolMagicId
│   │   └── volatile
│   ├── env
│   └── socket
└── preview
    ├── config
    │   ├── alonzo-genesis.json
    │   ├── byron-genesis.json
    │   ├── config-original.json
    │   ├── config.json
    │   ├── conway-genesis.json
    │   ├── shelley-genesis.json
    │   └── topology.json
    ├── db
    │   ├── immutable
    │   ├── ledger
    │   ├── lock
    │   ├── protocolMagicId
    │   └── volatile
    ├── env
    └── socket

19 directories, 33 files

$ du -h ~/.local/cardano/chains/
1.1M	/home/sean/.local/cardano/chains/mainnet/config
4.7G	/home/sean/.local/cardano/chains/mainnet/db/ledger
133M	/home/sean/.local/cardano/chains/mainnet/db/volatile
151G	/home/sean/.local/cardano/chains/mainnet/db/immutable
156G	/home/sean/.local/cardano/chains/mainnet/db
156G	/home/sean/.local/cardano/chains/mainnet
40K	/home/sean/.local/cardano/chains/preprod/config
5.2G	/home/sean/.local/cardano/chains/preprod/db/immutable
5.6M	/home/sean/.local/cardano/chains/preprod/db/volatile
475M	/home/sean/.local/cardano/chains/preprod/db/ledger
5.7G	/home/sean/.local/cardano/chains/preprod/db
5.7G	/home/sean/.local/cardano/chains/preprod
40K	/home/sean/.local/cardano/chains/preview/config
6.4G	/home/sean/.local/cardano/chains/preview/db/immutable
2.4M	/home/sean/.local/cardano/chains/preview/db/volatile
602M	/home/sean/.local/cardano/chains/preview/db/ledger
7.0G	/home/sean/.local/cardano/chains/preview/db
7.0G	/home/sean/.local/cardano/chains/preview
169G	/home/sean/.local/cardano/chains/

Changelog

2024-01-15:

  • Updated to Mithril v2347.0 and Cardano Node 8.7.3.
  • Changed location of software and chains to ~/.local/cardano/.
7 Likes

Very good article @HeptaSean Great work! I appreciate the guidelines

1 Like

@HeptaSean PreProd started. However, the mainnet is just stuck starting, over 45 minutes and counting…

Services are running tho,

image

image

You can look with journalctl --user -fu cnode@mainnet (if you did it similarly to my guide above, otherwise replace the service name with whatever you did) what it is currently doing.

It did a pretty time consuming rebuild when starting 8.7.3 for the first time for me too if I remember correctly. I think they changed some data structures again and the Mithril snapshots are still for the old (which is the safe thing to do because otherwise old versions could not be bootstrapped at all with them).

Edit: Found were I read that: https://discord.com/channels/826816523368005654/1014019542504185876/1197125022137991178:

Do we have a definite place which defines which cardano-node version is compatible with the downloaded snapshots?
Using 8.7.2 and 8.7.3 on mainnet snapshots right now will have it replay the ledger state (expected, as I think the snapshots were created by cardano-node 8.1.2?)

Yeah it makes sense. I’m looking at the logs. It looks like its replaying the ledger from scratch. It takes a long time. My guess would be somewhere around 3 hours at this rate.

Ouch. That is still less than synchronising without Mithril, but ouch.

Don’t think it was that long for me, but depends on a lot of things – memory, CPU, what else is running, …

30 Gb Ram 8 Cores, although I’m capping two cores for each node instance.

I see memory use growing, so that’s a good sign. I think it’s all good so far, it is just hard to tell if something is alright when it is not giving you much information about the current progress

Hmm, not sure what you are using to look at the log.

When my node did the replay it gave a message every couple of seconds:

Jan 15 14:37:21 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:21.99 UTC] Replaying ledger from genesis
Jan 15 14:37:22 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:22.19 UTC] Replayed block: slot 0 out of 112732111. Progress: 0.00%
Jan 15 14:37:24 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:24.29 UTC] Replayed block: slot 21599 out of 112732111. Progress: 0.02%
Jan 15 14:37:27 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:27.43 UTC] Replayed block: slot 43199 out of 112732111. Progress: 0.04%
Jan 15 14:37:30 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:30.00 UTC] Replayed block: slot 64799 out of 112732111. Progress: 0.06%
Jan 15 14:37:32 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 13:37:32.55 UTC] Replayed block: slot 86399 out of 112732111. Progress: 0.08%
[…]
Jan 15 19:48:52 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 18:48:52.22 UTC] Replayed block: slot 112643986 out of 112732111. Progress: 99.92%
Jan 15 19:48:57 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 18:48:57.84 UTC] Replayed block: slot 112665599 out of 112732111. Progress: 99.94%
Jan 15 19:49:05 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 18:49:05.84 UTC] Replayed block: slot 112687164 out of 112732111. Progress: 99.96%
Jan 15 19:49:10 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 18:49:10.11 UTC] Replayed block: slot 112708765 out of 112732111. Progress: 99.98%
Jan 15 19:49:15 nat cnode@mainnet[72665]: [nat:cardano.node.ChainDB:Info:5] [2024-01-15 18:49:15.87 UTC] Replayed block: slot 112730369 out of 112732111. Progress: 100.00%

And my memory was faulty: It did take more than five hours, it seems.

And as said: You can run journalctl --user --follow --unit cnode@mainnet or short form journalctl --user -fu cnode@mainnet to let it follow the log until you stop it with Ctrl-C. That way you see the newest progress messages popping up every couple of seconds in that terminal.

Yes, my journal is printing that the node is listening. No errors. I’m using the guild operators scripts, which may be why the logs are different. I only used the Mithril section from your guide :stuck_out_tongue:

1 Like