Thank you for your support and kind messages! It really energizes us to keep creating the best crypto guides. Use cointr.ee to find our donation addresses. 🙏
As of Feb 27 2020, this is guide version 3.1.0 and written for cardano mainnet with release v.1.25.1 😁
As a stake pool operator for Cardano, you will be competent with the following abilities:
operational knowledge of how to set up, run and maintain a Cardano node continuously
a commitment to maintain your node 24/7/365
system operation skills
server administration skills (operational and maintenance).
experience of development and operations (DevOps)
experience on how to harden and secure a server.
🛑 Before continuing this guide, you must satisfy the above requirements. 🚧
Two separate servers: 1 for block producer node, 1 for relay node
One air-gapped offline machine (cold environment)
Operating system: 64-bit Linux (i.e. Ubuntu Server 20.04 LTS)
Processor: An Intel or AMD x86 processor with two or more cores, at 2GHz or faster
Memory: 8GB of RAM
Storage: 20GB of free storage
Internet: Broadband internet connection with speeds at least 10 Mbps.
Data Plan: at least 1GB per hour. 720GB per month.
Power: Reliable electrical power
ADA balance: at least 505 ADA for pool deposit and transaction fees
Three separate servers: 1 for block producer node, 2 for relay nodes
One air-gapped offline machine (cold environment)
Operating system: 64-bit Linux (i.e. Ubuntu 20.04 LTS)
Processor: 4 core or higher CPU
Memory: 8GB+ of RAM
Storage: 256GB+ SSD
Internet: Broadband internet connections with speeds at least 100 Mbps
Data Plan: Unlimited
Power: Reliable electrical power with UPS
ADA balance: more pledge is better, to be determined by a0, the pledge influence factor
If you need ideas on how to harden your stake pool's nodes, refer to
If you need to install Ubuntu Server, refer to
For instructions on installing Ubuntu Desktop, refer to the following:
If you are rebuilding or reusing an existing cardano-node
installation, refer to section 18.2 on how to reset the installation.
If using Ubuntu Desktop, press Ctrl+Alt+T. This will launch a terminal window.
First, update packages and install Ubuntu dependencies.
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install git jq bc make automake rsync htop curl build-essential pkg-config libffi-dev libgmp-dev libssl-dev libtinfo-dev libsystemd-dev zlib1g-dev make g++ wget libncursesw5 libtool autoconf -y
Install Libsodium.
mkdir $HOME/gitcd $HOME/gitgit clone https://github.com/input-output-hk/libsodiumcd libsodiumgit checkout 66f017f1./autogen.sh./configuremakesudo make install
Debian OS pool operators: extra lib linking may be required.
sudo ln -s /usr/local/lib/libsodium.so.23.3.0 /usr/lib/libsodium.so.23
Install Cabal.
cdwget https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-install-3.2.0.0-x86_64-unknown-linux.tar.xztar -xf cabal-install-3.2.0.0-x86_64-unknown-linux.tar.xzrm cabal-install-3.2.0.0-x86_64-unknown-linux.tar.xz cabal.sigmkdir -p $HOME/.local/binmv cabal $HOME/.local/bin/
Install GHC.
wget https://downloads.haskell.org/ghc/8.10.2/ghc-8.10.2-x86_64-deb9-linux.tar.xztar -xf ghc-8.10.2-x86_64-deb9-linux.tar.xzrm ghc-8.10.2-x86_64-deb9-linux.tar.xzcd ghc-8.10.2./configuresudo make install
Update PATH to include Cabal and GHC and add exports. Your node's location will be in $NODE_HOME. The cluster configuration is set by $NODE_CONFIG and $NODE_BUILD_NUM.
echo PATH="$HOME/.local/bin:$PATH" >> $HOME/.bashrcecho export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrcecho export NODE_HOME=$HOME/cardano-my-node >> $HOME/.bashrcecho export NODE_CONFIG=mainnet>> $HOME/.bashrcecho export NODE_BUILD_NUM=$(curl https://hydra.iohk.io/job/Cardano/iohk-nix/cardano-deployment/latest-finished/download/1/index.html | grep -e "build" | sed 's/.*build\/\([0-9]*\)\/download.*/\1/g') >> $HOME/.bashrcsource $HOME/.bashrc
Update cabal and verify the correct versions were installed successfully.
cabal updatecabal -Vghc -V
Cabal library should be version 3.2.0.0 and GHC should be version 8.10.2
Download source code and switch to the latest tag.
cd $HOME/gitgit clone https://github.com/input-output-hk/cardano-node.gitcd cardano-nodegit fetch --all --recurse-submodules --tagsgit checkout tags/1.25.1
Configure build options.
cabal configure -O0 -w ghc-8.10.2
Update the cabal config, project settings, and reset build folder.
echo -e "package cardano-crypto-praos\n flags: -external-libsodium-vrf" > cabal.project.localsed -i $HOME/.cabal/config -e "s/overwrite-policy:/overwrite-policy: always/g"rm -rf $HOME/git/cardano-node/dist-newstyle/build/x86_64-linux/ghc-8.10.2
Build the cardano-node from source code.
cabal build cardano-cli cardano-node
Building process may take a few minutes up to a few hours depending on your computer's processing power.
Copy cardano-cli and cardano-node files into bin directory.
sudo cp $(find $HOME/git/cardano-node/dist-newstyle/build -type f -name "cardano-cli") /usr/local/bin/cardano-cli
sudo cp $(find $HOME/git/cardano-node/dist-newstyle/build -type f -name "cardano-node") /usr/local/bin/cardano-node
Verify your cardano-cli and cardano-node are the expected versions.
cardano-node versioncardano-cli version
Here you'll grab the config.json, genesis.json, and topology.json files needed to configure your node.
mkdir $NODE_HOMEcd $NODE_HOMEwget -N https://hydra.iohk.io/build/${NODE_BUILD_NUM}/download/1/${NODE_CONFIG}-byron-genesis.jsonwget -N https://hydra.iohk.io/build/${NODE_BUILD_NUM}/download/1/${NODE_CONFIG}-topology.jsonwget -N https://hydra.iohk.io/build/${NODE_BUILD_NUM}/download/1/${NODE_CONFIG}-shelley-genesis.jsonwget -N https://hydra.iohk.io/build/${NODE_BUILD_NUM}/download/1/${NODE_CONFIG}-config.json
Run the following to modify mainnet-config.json and
update TraceBlockFetchDecisions to "true"
sed -i ${NODE_CONFIG}-config.json \-e "s/TraceBlockFetchDecisions\": false/TraceBlockFetchDecisions\": true/g"
✨ Tip for relay nodes: It's possible to reduce memory and cpu usage by setting "TraceMemPool" to "false" in mainnet-config.json
Update .bashrc shell variables.
echo export CARDANO_NODE_SOCKET_PATH="$NODE_HOME/db/socket" >> $HOME/.bashrcsource $HOME/.bashrc
A block producer node will be configured with various key-pairs needed for block generation (cold keys, KES hot keys and VRF hot keys). It can only connect to its relay nodes.
A relay node will not be in possession of any keys and will therefore be unable to produce blocks. It will be connected to its block-producing node, other relays and external nodes.
For the purposes of this guide, we will be building two nodes on two separate servers. One node will be designated the block producer node, and the other will be the relay node, named relaynode1.
Configure topology.json file so that
relay node(s) connect to public relay nodes (like IOHK and buddy relay nodes) and your block-producer node
block-producer node only connects to your relay node(s)
On your block-producer node, run the following. Update the addr with your relay node's public IP address.
cat > $NODE_HOME/${NODE_CONFIG}-topology.json << EOF{"Producers": [{"addr": "<RELAYNODE1'S PUBLIC IP ADDRESS>","port": 6000,"valency": 1}]}EOF
🚧 On your other server that will be designed as your relay node or what we will call relaynode1 throughout this guide, carefully repeat steps 1 through 3 in order to build the cardano binaries.
You can have multiple relay nodes as you scale up your stake pool architecture. Simply create relaynodeN and adapt the guide instructions accordingly.
On your relaynode1, run with the following after updating with your block producer's public IP address.
cat > $NODE_HOME/${NODE_CONFIG}-topology.json << EOF{"Producers": [{"addr": "<BLOCK PRODUCER NODE'S PUBLIC IP ADDRESS>","port": 6000,"valency": 1},{"addr": "relays-new.cardano-mainnet.iohk.io","port": 3001,"valency": 2}]}EOF
Valency tells the node how many connections to keep open. Only DNS addresses are affected. If value is 0, the address is ignored.
✨ Port Forwarding Tip: You'll need to forward and open ports 6000 to your nodes. Check with https://www.yougetsignal.com/tools/open-ports/ or https://canyouseeme.org/ .
An air-gapped offline machine is called your cold environment.
Protects against key-logging attacks, malware/virus based attacks and other firewall or security exploits.
Physically isolated from the rest of your network.
Must not have a network connection, wired or wireless.
Is not a VM on a machine with a network connection.
Learn more about air-gapping at wikipedia.
echo export NODE_HOME=$HOME/cardano-my-node >> $HOME/.bashrcsource $HOME/.bashrcmkdir -p $NODE_HOME
Copy from your hot environment, also known as your block producer node, a copy of the cardano-cli
binaries to your cold environment, this air-gapped offline machine.
In order to remain a true air-gapped environment, you must move files physically between your cold and hot environments with USB keys or other removable media.
The startup script contains all the variables needed to run a cardano-node such as directory, port, db path, config file, and topology file.
cat > $NODE_HOME/startBlockProducingNode.sh << EOF#!/bin/bashDIRECTORY=$NODE_HOMEPORT=6000HOSTADDR=0.0.0.0TOPOLOGY=\${DIRECTORY}/${NODE_CONFIG}-topology.jsonDB_PATH=\${DIRECTORY}/dbSOCKET_PATH=\${DIRECTORY}/db/socketCONFIG=\${DIRECTORY}/${NODE_CONFIG}-config.json/usr/local/bin/cardano-node run --topology \${TOPOLOGY} --database-path \${DB_PATH} --socket-path \${SOCKET_PATH} --host-addr \${HOSTADDR} --port \${PORT} --config \${CONFIG}EOF
cat > $NODE_HOME/startRelayNode1.sh << EOF#!/bin/bashDIRECTORY=$NODE_HOMEPORT=6000HOSTADDR=0.0.0.0TOPOLOGY=\${DIRECTORY}/${NODE_CONFIG}-topology.jsonDB_PATH=\${DIRECTORY}/dbSOCKET_PATH=\${DIRECTORY}/db/socketCONFIG=\${DIRECTORY}/${NODE_CONFIG}-config.json/usr/local/bin/cardano-node run --topology \${TOPOLOGY} --database-path \${DB_PATH} --socket-path \${SOCKET_PATH} --host-addr \${HOSTADDR} --port \${PORT} --config \${CONFIG}EOF
Add execute permissions to the startup script.
chmod +x $NODE_HOME/startBlockProducingNode.sh
chmod +x $NODE_HOME/startRelayNode1.sh
Run the following to create a systemd unit file to define yourcardano-node.service
configuration.
Auto-start your stake pool when the computer reboots due to maintenance, power outage, etc.
Automatically restart crashed stake pool processes.
Maximize your stake pool up-time and performance.
cat > $NODE_HOME/cardano-node.service << EOF# The Cardano node service (part of systemd)# file: /etc/systemd/system/cardano-node.service[Unit]Description = Cardano node serviceWants = network-online.targetAfter = network-online.target[Service]User = ${USER}Type = simpleWorkingDirectory= ${NODE_HOME}ExecStart = /bin/bash -c '${NODE_HOME}/startBlockProducingNode.sh'KillSignal=SIGINTRestartKillSignal=SIGINTTimeoutStopSec=2LimitNOFILE=32768Restart=alwaysRestartSec=5[Install]WantedBy = multi-user.targetEOF
cat > $NODE_HOME/cardano-node.service << EOF# The Cardano node service (part of systemd)# file: /etc/systemd/system/cardano-node.service[Unit]Description = Cardano node serviceWants = network-online.targetAfter = network-online.target[Service]User = ${USER}Type = simpleWorkingDirectory= ${NODE_HOME}ExecStart = /bin/bash -c '${NODE_HOME}/startRelayNode1.sh'KillSignal=SIGINTRestartKillSignal=SIGINTTimeoutStopSec=2LimitNOFILE=32768Restart=alwaysRestartSec=5[Install]WantedBy = multi-user.targetEOF
Move the unit file to /etc/systemd/system
and give it permissions.
sudo mv $NODE_HOME/cardano-node.service /etc/systemd/system/cardano-node.service
sudo chmod 644 /etc/systemd/system/cardano-node.service
Run the following to enable auto-starting of your stake pool at boot time.
sudo systemctl daemon-reloadsudo systemctl enable cardano-node
Your stake pool is now managed by the reliability and robustness of systemd. Below are some commands for using systemd.
sudo systemctl status cardano-node
sudo systemctl reload-or-restart cardano-node
sudo systemctl stop cardano-node
journalctl --unit=cardano-node --follow
journalctl --unit=cardano-node --since=yesterday
journalctl --unit=cardano-node --since=today
journalctl --unit=cardano-node --since='2020-07-29 00:00:00' --until='2020-07-29 12:00:00'
Start your stake pool with systemctl and begin syncing the blockchain!
sudo systemctl start cardano-node
sudo systemctl start cardano-node
Install gLiveView, a monitoring tool.
gLiveView displays crucial node status information and works well with systemd services. Credits to the Guild Operators for creating this tool.
cd $NODE_HOMEsudo apt install bc tcptraceroute -ycurl -s -o gLiveView.sh https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/gLiveView.shcurl -s -o env https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/envchmod 755 gLiveView.sh
Run the following to modify env with the updated file locations.
sed -i env \-e "s/\#CONFIG=\"\${CNODE_HOME}\/files\/config.json\"/CONFIG=\"\${NODE_HOME}\/mainnet-config.json\"/g" \-e "s/\#SOCKET=\"\${CNODE_HOME}\/sockets\/node0.socket\"/SOCKET=\"\${NODE_HOME}\/db\/socket\"/g"
A node must reach epoch 208 (Shelley launch) before gLiveView.sh can start tracking the node syncing. You can track the node syncing using journalctl
before epoch 208.
journalctl --unit=cardano-node --follow
Run gLiveView to monitor the progress of the sync'ing of the blockchain.
./gLiveView.sh
Sample output of gLiveView.
For more information, refer to the official Guild Live View docs.
✨ Pro tip: If you synchronize a node's database, you can copy the database directory over to your other node directly and save time.
Congratulations! Your node is running successfully now. Let it sync up.
The block-producer node requires you to create 3 keys as defined in the Shelley ledger specs:
stake pool cold key (node.cert)
stake pool hot key (kes.skey)
stake pool VRF key (vrf.skey)
First, make a KES key pair.
cd $NODE_HOMEcardano-cli node key-gen-KES \--verification-key-file kes.vkey \--signing-key-file kes.skey
KES (key evolving signature) keys are created to secure your stake pool against hackers who might compromise your keys.
On mainnet, you will need to regenerate the KES key every 90 days.
🔥 Cold keys must be generated and stored on your air-gapped offline machine. The cold keys are the files stored in $HOME/cold-keys.
Make a directory to store your cold keys
mkdir $HOME/cold-keyspushd $HOME/cold-keys
Make a set of cold keys and create the cold counter file.
cardano-cli node key-gen \--cold-verification-key-file node.vkey \--cold-signing-key-file node.skey \--operational-certificate-issue-counter node.counter
Be sure to back up your all your keys to another secure storage device. Make multiple copies.
Determine the number of slots per KES period from the genesis file.
pushd +1slotsPerKESPeriod=$(cat $NODE_HOME/${NODE_CONFIG}-shelley-genesis.json | jq -r '.slotsPerKESPeriod')echo slotsPerKESPeriod: ${slotsPerKESPeriod}
Before continuing, your node must be fully synchronized to the blockchain. Otherwise, you won't calculate the latest KES period. Your node is synchronized when the epoch and slot# is equal to that found on a block explorer such as https://pooltool.io/
slotNo=$(cardano-cli query tip --mainnet | jq -r '.slotNo')echo slotNo: ${slotNo}
Find the kesPeriod by dividing the slot tip number by the slotsPerKESPeriod.
kesPeriod=$((${slotNo} / ${slotsPerKESPeriod}))echo kesPeriod: ${kesPeriod}startKesPeriod=${kesPeriod}echo startKesPeriod: ${startKesPeriod}
With this calculation, you can generate a operational certificate for your pool.
Copy kes.vkey to your cold environment.
Change the <startKesPeriod> value accordingly.
Stake pool operators must provide an operational certificate to verify that the pool has the authority to run. The certificate includes the operator’s signature, and includes key information about the pool (addresses, keys, etc.). Operational certificates represent the link between the operator’s offline key and their operational key.
cardano-cli node issue-op-cert \--kes-verification-key-file kes.vkey \--cold-signing-key-file $HOME/cold-keys/node.skey \--operational-certificate-issue-counter $HOME/cold-keys/node.counter \--kes-period <startKesPeriod> \--out-file node.cert
Copy node.cert to your hot environment.
Make a VRF key pair.
cardano-cli node key-gen-VRF \--verification-key-file vrf.vkey \--signing-key-file vrf.skey
Update vrf key permissions to read-only.
chmod 400 vrf.skey
Stop your stake pool by running the following:
sudo systemctl stop cardano-node
Update your startup script with the new KES, VRF and Operation Certificate.
cat > $NODE_HOME/startBlockProducingNode.sh << EOFDIRECTORY=$NODE_HOMEPORT=6000HOSTADDR=0.0.0.0TOPOLOGY=\${DIRECTORY}/${NODE_CONFIG}-topology.jsonDB_PATH=\${DIRECTORY}/dbSOCKET_PATH=\${DIRECTORY}/db/socketCONFIG=\${DIRECTORY}/${NODE_CONFIG}-config.jsonKES=\${DIRECTORY}/kes.skeyVRF=\${DIRECTORY}/vrf.skeyCERT=\${DIRECTORY}/node.certcardano-node run --topology \${TOPOLOGY} --database-path \${DB_PATH} --socket-path \${SOCKET_PATH} --host-addr \${HOSTADDR} --port \${PORT} --config \${CONFIG} --shelley-kes-key \${KES} --shelley-vrf-key \${VRF} --shelley-operational-certificate \${CERT}EOF
To operate a stake pool, you need the KES, VRF key and Operational Certificate. Cold keys generate new operational certificates periodically.
Now start your block producer node.
sudo systemctl start cardano-node# Monitor with gLiveView./gLiveView.sh
First, obtain the protocol-parameters.
Wait for the block-producing node to start syncing before continuing if you get this error message.
cardano-cli: Network.Socket.connect: : does not exist (No such file or directory)
cardano-cli query protocol-parameters \--mainnet \--allegra-era \--out-file params.json
Payment keys are used to send and receive payments and stake keys are used to manage stake delegations.
There are two ways to create your payment
and stake
key pair. Pick the one that best suits your needs.
🔥 Critical Operational Security Advice: payment
and stake
keys must be generated and used to build transactions in an cold environment. In other words, your air-gapped offline machine. Copy cardano-cli
binary over to your offline machine and run the CLI method or mnemonic method. The only steps performed online in a hot environment are those steps that require live data. Namely the follow type of steps:
querying the current slot tip
querying the balance of an address
submitting a transaction
Create a new payment key pair: payment.skey
& payment.vkey
###### On air-gapped offline machine,###cd $NODE_HOMEcardano-cli address key-gen \--verification-key-file payment.vkey \--signing-key-file payment.skey
Create a new stake address key pair: stake.skey
& stake.vkey
###### On air-gapped offline machine,###cardano-cli stake-address key-gen \--verification-key-file stake.vkey \--signing-key-file stake.skey
Create your stake address from the stake address verification key and store it in stake.addr
###### On air-gapped offline machine,###cardano-cli stake-address build \--stake-verification-key-file stake.vkey \--out-file stake.addr \--mainnet
Build a payment address for the payment key payment.vkey
which will delegate to the stake address, stake.vkey
###### On air-gapped offline machine,###cardano-cli address build \--payment-verification-key-file payment.vkey \--stake-verification-key-file stake.vkey \--out-file payment.addr \--mainnet
Credits to ilap for creating this process.
Benefits: Track and control pool rewards from any wallet (Daedalus, Yoroi or any other wallet) that support stakings.
Create a 15-word or 24-word length shelley compatible mnemonic with Daedalus or Yoroi on a offline machine preferred.
Using your online block producer node, download cardano-wallet
###### On block producer node,###cd $NODE_HOMEwget https://hydra.iohk.io/build/3662127/download/1/cardano-wallet-shelley-2020.7.28-linux64.tar.gz
Verify the legitimacy of cardano-wallet
by checking the sha256 hash found in the Details button.
echo "f75e5b2b4cc5f373d6b1c1235818bcab696d86232cb2c5905b2d91b4805bae84 *cardano-wallet-shelley-2020.7.28-linux64.tar.gz" | shasum -a 256 --check
Example valid output:
cardano-wallet-shelley-2020.7.28-linux64.tar.gz: OK
Only proceed if the sha256 check passes with OK!
Transfer the cardano-wallet to your air-gapped offline machine via USB key or other removable media.
Extract the wallet files and cleanup.
###### On air-gapped offline machine,###tar -xvf cardano-wallet-shelley-2020.7.28-linux64.tar.gzrm cardano-wallet-shelley-2020.7.28-linux64.tar.gz
CreateextractPoolStakingKeys.sh
script.
###### On air-gapped offline machine,###cat > extractPoolStakingKeys.sh << HERE#!/bin/bashCADDR=\${CADDR:=\$( which cardano-address )}[[ -z "\$CADDR" ]] && ( echo "cardano-address cannot be found, exiting..." >&2 ; exit 127 )CCLI=\${CCLI:=\$( which cardano-cli )}[[ -z "\$CCLI" ]] && ( echo "cardano-cli cannot be found, exiting..." >&2 ; exit 127 )OUT_DIR="\$1"[[ -e "\$OUT_DIR" ]] && {echo "The \"\$OUT_DIR\" is already exist delete and run again." >&2exit 127} || mkdir -p "\$OUT_DIR" && pushd "\$OUT_DIR" >/dev/nullshiftMNEMONIC="\$*"# Generate the master key from mnemonics and derive the stake account keys# as extended private and public keys (xpub, xprv)echo "\$MNEMONIC" |\"\$CADDR" key from-recovery-phrase Shelley > root.prvcat root.prv |\"\$CADDR" key child 1852H/1815H/0H/2/0 > stake.xprvcat root.prv |\"\$CADDR" key child 1852H/1815H/0H/0/0 > payment.xprvTESTNET=0MAINNET=1NETWORK=\$MAINNETcat payment.xprv |\"\$CADDR" key public | tee payment.xpub |\"\$CADDR" address payment --network-tag \$NETWORK |\"\$CADDR" address delegation \$(cat stake.xprv | "\$CADDR" key public | tee stake.xpub) |\tee base.addr_candidate |\"\$CADDR" address inspectecho "Generated from 1852H/1815H/0H/{0,2}/0"cat base.addr_candidateecho# XPrv/XPub conversion to normal private and public key, keep in mind the# keypars are not a valind Ed25519 signing keypairs.TESTNET_MAGIC="--testnet-magic 42"MAINNET_MAGIC="--mainnet"MAGIC="\$MAINNET_MAGIC"SESKEY=\$( cat stake.xprv | bech32 | cut -b -128 )\$( cat stake.xpub | bech32)PESKEY=\$( cat payment.xprv | bech32 | cut -b -128 )\$( cat payment.xpub | bech32)cat << EOF > stake.skey{"type": "StakeExtendedSigningKeyShelley_ed25519_bip32","description": "","cborHex": "5880\$SESKEY"}EOFcat << EOF > payment.skey{"type": "PaymentExtendedSigningKeyShelley_ed25519_bip32","description": "Payment Signing Key","cborHex": "5880\$PESKEY"}EOF"\$CCLI" shelley key verification-key --signing-key-file stake.skey --verification-key-file stake.evkey"\$CCLI" shelley key verification-key --signing-key-file payment.skey --verification-key-file payment.evkey"\$CCLI" shelley key non-extended-key --extended-verification-key-file payment.evkey --verification-key-file payment.vkey"\$CCLI" shelley key non-extended-key --extended-verification-key-file stake.evkey --verification-key-file stake.vkey"\$CCLI" shelley stake-address build --stake-verification-key-file stake.vkey \$MAGIC > stake.addr"\$CCLI" shelley address build --payment-verification-key-file payment.vkey \$MAGIC > payment.addr"\$CCLI" shelley address build \--payment-verification-key-file payment.vkey \--stake-verification-key-file stake.vkey \\$MAGIC > base.addrecho "Important the base.addr and the base.addr_candidate must be the same"diff base.addr base.addr_candidatepopd >/dev/nullHERE
Add permissions and export PATH to use the binaries.
###### On air-gapped offline machine,###chmod +x extractPoolStakingKeys.shexport PATH="$(pwd)/cardano-wallet-shelley-2020.7.28:$PATH"
Extract your keys. Update the command with your mnemonic phrase.
###### On air-gapped offline machine,###./extractPoolStakingKeys.sh extractedPoolKeys/ <15|24-word length mnemonic>
Important: The base.addr and the base.addr_candidate must be the same. Review the screen output.
Your new staking keys are in the folder extractedPoolKeys/
Now move payment/stake
key pair over to your $NODE_HOME
for use with your stake pool.
###### On air-gapped offline machine,###cd extractedPoolKeys/cp stake.vkey stake.skey stake.addr payment.vkey payment.skey base.addr $NODE_HOMEcd $NODE_HOME#Rename to base.addr file to payment.addrmv base.addr payment.addr
payment.addr, or also known as base.addr from this extraction script, will be the cardano address which holds your pool's pledge.
Clear the bash history in order to protect your mnemonic phrase and remove the cardano-wallet
files.
###### On air-gapped offline machine,###history -c && history -wrm -rf $NODE_HOME/cardano-wallet-shelley-2020.7.28
Finally close all your terminal windows and open new ones with zero history.
Awesome. Now you can track your pool rewards in your wallet.
Next step is to fund your payment address.
Copy payment.addr to your hot environment.
Payment address can be funded from your Daedalus / Yoroi wallet.
Run the following to find your payment address.
cat payment.addr
After funding your account, check your payment address balance.
Before continuing, your nodes must be fully synchronized to the blockchain. Otherwise, you won't see your funds.
cardano-cli query utxo \--address $(cat payment.addr) \--allegra-era \--mainnet
You should see output similar to this. This is your unspent transaction output (UXTO).
TxHash TxIx Lovelace----------------------------------------------------------------------------------------100322a39d02c2ead.... 0 1000000000
Create a certificate, stake.cert
, using the stake.vkey
cardano-cli stake-address registration-certificate \--stake-verification-key-file stake.vkey \--out-file stake.cert
Copy stake.cert to your hot environment.
You need to find the tip of the blockchain to set the invalid-hereafter parameter properly.
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slotNo')echo Current Slot: $currentSlot
Find your balance and UTXOs.
cardano-cli query utxo \--address $(cat payment.addr) \--allegra-era \--mainnet > fullUtxo.outtail -n +3 fullUtxo.out | sort -k3 -nr > balance.outcat balance.outtx_in=""total_balance=0while read -r utxo; doin_addr=$(awk '{ print $1 }' <<< "${utxo}")idx=$(awk '{ print $2 }' <<< "${utxo}")utxo_balance=$(awk '{ print $3 }' <<< "${utxo}")total_balance=$((${total_balance}+${utxo_balance}))echo TxHash: ${in_addr}#${idx}echo ADA: ${utxo_balance}tx_in="${tx_in} --tx-in ${in_addr}#${idx}"done < balance.outtxcnt=$(cat balance.out | wc -l)echo Total ADA balance: ${total_balance}echo Number of UTXOs: ${txcnt}
Find the keyDeposit value.
keyDeposit=$(cat $NODE_HOME/params.json | jq -r '.keyDeposit')echo keyDeposit: $keyDeposit
Registration of a stake address certificate (keyDeposit) costs 2000000 lovelace.
Run the build-raw transaction command
The invalid-hereafter value must be greater than the current tip. In this example, we use current slot + 10000.
cardano-cli transaction build-raw \${tx_in} \--tx-out $(cat payment.addr)+0 \--invalid-hereafter $(( ${currentSlot} + 10000)) \--fee 0 \--out-file tx.tmp \--allegra-era \--certificate stake.cert
Calculate the current minimum fee:
fee=$(cardano-cli transaction calculate-min-fee \--tx-body-file tx.tmp \--tx-in-count ${txcnt} \--tx-out-count 1 \--mainnet \--witness-count 2 \--byron-witness-count 0 \--protocol-params-file params.json | awk '{ print $1 }')echo fee: $fee
Ensure your balance is greater than cost of fee + keyDeposit or this will not work.
Calculate your change output.
txOut=$((${total_balance}-${keyDeposit}-${fee}))echo Change Output: ${txOut}
Build your transaction which will register your stake address.
cardano-cli transaction build-raw \${tx_in} \--tx-out $(cat payment.addr)+${txOut} \--invalid-hereafter $(( ${currentSlot} + 10000)) \--fee ${fee} \--certificate-file stake.cert \--allegra-era \--out-file tx.raw
Copy tx.raw to your cold environment.
Sign the transaction with both the payment and stake secret keys.
cardano-cli transaction sign \--tx-body-file tx.raw \--signing-key-file payment.skey \--signing-key-file stake.skey \--mainnet \--out-file tx.signed
Copy tx.signed to your hot environment.
Send the signed transaction.
cardano-cli transaction submit \--tx-file tx.signed \--mainnet
Create your pool's metadata with a JSON file. Update with your pool information.
ticker must be between 3-5 characters in length. Characters must be A-Z and 0-9 only.
description cannot exceed 255 characters in length.
cat > poolMetaData.json << EOF{"name": "MyPoolName","description": "My pool description","ticker": "MPN","homepage": "https://myadapoolnamerocks.com"}EOF
Calculate the hash of your metadata file.
cardano-cli stake-pool metadata-hash --pool-metadata-file poolMetaData.json > poolMetaDataHash.txt
Now upload your poolMetaData.json to your website or a public website such as https://pages.github.com/
Refer to the following quick guide if you need help hosting your metadata on github.com
Find the minimum pool cost.
minPoolCost=$(cat $NODE_HOME/params.json | jq -r .minPoolCost)echo minPoolCost: ${minPoolCost}
minPoolCost is 340000000 lovelace or 340 ADA. Therefore, your --pool-cost
must be at a minimum this amount.
Create a registration certificate for your stake pool. Update with your metadata URL and your relay node information. Choose one of the three options available to configure relay nodes -- DNS based, Round Robin DNS based, or IP based.
DNS based relays are recommended for simplicity of node management. In other words, you don't need to re-submit this registration certificate transaction every time your IP changes. Also you can easily update the DNS to point towards a new IP should you re-locate or re-build a relay node, for example.
Update the next operation
cardano-cli stake-pool registration-certificate
to be run on your air-gapped offline machine appropriately.
DNS based relays, 1 entry per DNS record
--single-host-pool-relay relaynode1.myadapoolnamerocks.com\--pool-relay-port 6000 \--single-host-pool-relay relaynode2.myadapoolnamerocks.com\--pool-relay-port 6000 \
Round Robin DNS based relays, 1 entry per SRV DNS record
--multi-host-pool-relay relayNodes.myadapoolnamerocks.com\--pool-relay-port 6000 \
IP based relays, 1 entry per IP address
--pool-relay-port 6000 \--pool-relay-ipv4 <your first relay node public IP address> \--pool-relay-port 6000 \--pool-relay-ipv4 <your second relay node public IP address> \
metadata-url must be no longer than 64 characters.
cardano-cli stake-pool registration-certificate \--cold-verification-key-file $HOME/cold-keys/node.vkey \--vrf-verification-key-file vrf.vkey \--pool-pledge 100000000 \--pool-cost 345000000 \--pool-margin 0.15 \--pool-reward-account-verification-key-file stake.vkey \--pool-owner-stake-verification-key-file stake.vkey \--mainnet \--single-host-pool-relay <dns based relay, example ~ relaynode1.myadapoolnamerocks.com> \--pool-relay-port 6000 \--metadata-url <url where you uploaded poolMetaData.json> \--metadata-hash $(cat poolMetaDataHash.txt) \--out-file pool.cert
Here we are pledging 100 ADA with a fixed pool cost of 345 ADA and a pool margin of 15%.
Copy pool.cert to your hot environment.
Pledge stake to your stake pool.
cardano-cli stake-address delegation-certificate \--stake-verification-key-file stake.vkey \--cold-verification-key-file $HOME/cold-keys/node.vkey \--out-file deleg.cert
Copy deleg.cert to your hot environment.
This operation creates a delegation certificate which delegates funds from all stake addresses associated with key stake.vkey
to the pool belonging to cold key node.vkey
A stake pool owner's promise to fund their own pool is called Pledge.
Your balance needs to be greater than the pledge amount.
You pledge funds are not moved anywhere. In this guide's example, the pledge remains in the stake pool's owner keys, specifically payment.addr
Failing to fulfill pledge will result in missed block minting opportunities and your delegators would miss rewards.
Your pledge is not locked up. You are free to transfer your funds.
You need to find the tip of the blockchain to set the invalid-hereafter parameter properly.
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slotNo')echo Current Slot: $currentSlot
Find your balance and UTXOs.
cardano-cli query utxo \--address $(cat payment.addr) \--allegra-era \--mainnet > fullUtxo.outtail -n +3 fullUtxo.out | sort -k3 -nr > balance.outcat balance.outtx_in=""total_balance=0while read -r utxo; doin_addr=$(awk '{ print $1 }' <<< "${utxo}")idx=$(awk '{ print $2 }' <<< "${utxo}")utxo_balance=$(awk '{ print $3 }' <<< "${utxo}")total_balance=$((${total_balance}+${utxo_balance}))echo TxHash: ${in_addr}#${idx}echo ADA: ${utxo_balance}tx_in="${tx_in} --tx-in ${in_addr}#${idx}"done < balance.outtxcnt=$(cat balance.out | wc -l)echo Total ADA balance: ${total_balance}echo Number of UTXOs: ${txcnt}
Find the deposit fee for a pool.
poolDeposit=$(cat $NODE_HOME/params.json | jq -r '.poolDeposit')echo poolDeposit: $poolDeposit
Run the build-raw transaction command.
The invalid-hereafter value must be greater than the current tip. In this example, we use current slot + 10000.
cardano-cli transaction build-raw \${tx_in} \--tx-out $(cat payment.addr)+$(( ${total_balance} - ${poolDeposit})) \--invalid-hereafter $(( ${currentSlot} + 10000)) \--fee 0 \--certificate-file pool.cert \--certificate-file deleg.cert \--allegra-era \--out-file tx.tmp
Calculate the minimum fee:
fee=$(cardano-cli transaction calculate-min-fee \--tx-body-file tx.tmp \--tx-in-count ${txcnt} \--tx-out-count 1 \--mainnet \--witness-count 3 \--byron-witness-count 0 \--protocol-params-file params.json | awk '{ print $1 }')echo fee: $fee
Ensure your balance is greater than cost of fee + minPoolCost or this will not work.
Calculate your change output.
txOut=$((${total_balance}-${poolDeposit}-${fee}))echo txOut: ${txOut}
Build the transaction.
cardano-cli transaction build-raw \${tx_in} \--tx-out $(cat payment.addr)+${txOut} \--invalid-hereafter $(( ${currentSlot} + 10000)) \--fee ${fee} \--certificate-file pool.cert \--certificate-file deleg.cert \--allegra-era \--out-file tx.raw
Copy tx.raw to your cold environment.
Sign the transaction.
cardano-cli transaction sign \--tx-body-file tx.raw \--signing-key-file payment.skey \--signing-key-file $HOME/cold-keys/node.skey \--signing-key-file stake.skey \--mainnet \--out-file tx.signed
Copy tx.signed to your hot environment.
Send the transaction.
cardano-cli transaction submit \--tx-file tx.signed \--mainnet
Your stake pool ID can be computed with:
cardano-cli stake-pool id --cold-verification-key-file $HOME/cold-keys/node.vkey --output-format hex > stakepoolid.txtcat stakepoolid.txt
Copy stakepoolid.txt to your hot environment.
Now that you have your stake pool ID, verify it's included in the blockchain.
cardano-cli query ledger-state --mainnet --allegra-era | grep publicKey | grep $(cat stakepoolid.txt)
A non-empty string return means you're registered! 👏
With your stake pool ID, now you can find your data on block explorers such as https://pooltool.io/
Shelley has been launched without peer-to-peer (p2p) node discovery so that means we will need to manually add trusted nodes in order to configure our topology. This is a critical step as skipping this step will result in your minted blocks being orphaned by the rest of the network.
Configure your topology files.
topologyUpdate.sh method is automated and works after 4 hours.
[ Retired ] Pooltool.io method gives you control over who your nodes connect to.
Credits to GROWPOOL for this addition and credits to CNTOOLS Guild OPS on creating this process.
Create the topologyUpdater.sh
script which publishes your node information to a topology fetch list.
###### On relaynode1###cat > $NODE_HOME/topologyUpdater.sh << EOF#!/bin/bash# shellcheck disable=SC2086,SC2034USERNAME=$(whoami)CNODE_PORT=6000 # must match your relay node port as set in the startup commandCNODE_HOSTNAME="CHANGE ME" # optional. must resolve to the IP you are requesting fromCNODE_BIN="/usr/local/bin"CNODE_HOME=$NODE_HOMECNODE_LOG_DIR="\${CNODE_HOME}/logs"GENESIS_JSON="\${CNODE_HOME}/${NODE_CONFIG}-shelley-genesis.json"NETWORKID=\$(jq -r .networkId \$GENESIS_JSON)CNODE_VALENCY=1 # optional for multi-IP hostnamesNWMAGIC=\$(jq -r .networkMagic < \$GENESIS_JSON)[[ "\${NETWORKID}" = "Mainnet" ]] && HASH_IDENTIFIER="--mainnet" || HASH_IDENTIFIER="--testnet-magic \${NWMAGIC}"[[ "\${NWMAGIC}" = "764824073" ]] && NETWORK_IDENTIFIER="--mainnet" || NETWORK_IDENTIFIER="--testnet-magic \${NWMAGIC}"export PATH="\${CNODE_BIN}:\${PATH}"export CARDANO_NODE_SOCKET_PATH="\${CNODE_HOME}/db/socket"blockNo=\$(/usr/local/bin/cardano-cli query tip \${NETWORK_IDENTIFIER} | jq -r .blockNo )# Note:# if you run your node in IPv4/IPv6 dual stack network configuration and want announced the# IPv4 address only please add the -4 parameter to the curl command below (curl -4 -s ...)if [ "\${CNODE_HOSTNAME}" != "CHANGE ME" ]; thenT_HOSTNAME="&hostname=\${CNODE_HOSTNAME}"elseT_HOSTNAME=''fiif [ ! -d \${CNODE_LOG_DIR} ]; thenmkdir -p \${CNODE_LOG_DIR};ficurl -s "https://api.clio.one/htopology/v1/?port=\${CNODE_PORT}&blockNo=\${blockNo}&valency=\${CNODE_VALENCY}&magic=\${NWMAGIC}\${T_HOSTNAME}" | tee -a \$CNODE_LOG_DIR/topologyUpdater_lastresult.jsonEOF
Add permissions and run the updater script.
###### On relaynode1###cd $NODE_HOMEchmod +x topologyUpdater.sh./topologyUpdater.sh
When the topologyUpdater.sh
runs successfully, you will see
{ "resultcode": "201", "datetime":"2020-07-28 01:23:45", "clientIp": "1.2.3.4", "iptype": 4, "msg": "nice to meet you" }
Every time the script runs and updates your IP, a log is created in $NODE_HOME/logs
Add a crontab job to automatically run topologyUpdater.sh
every hour on the 22nd minute. You can change the 22 value to your own preference.
###### On relaynode1###cat > $NODE_HOME/crontab-fragment.txt << EOF22 * * * * ${NODE_HOME}/topologyUpdater.shEOFcrontab -l | cat - crontab-fragment.txt >crontab.txt && crontab crontab.txtrm crontab-fragment.txt
After four hours and four updates, your node IP will be registered in the topology fetch list.
Complete this section after four hours when your relay node IP is properly registered.
Create relay-topology_pull.sh
script which fetches your relay node buddies and updates your topology file. Update with your block producer's public IP address.
###### On relaynode1###cat > $NODE_HOME/relay-topology_pull.sh << EOF#!/bin/bashBLOCKPRODUCING_IP=<BLOCK PRODUCERS PUBLIC IP ADDRESS>BLOCKPRODUCING_PORT=6000curl -s -o $NODE_HOME/${NODE_CONFIG}-topology.json "https://api.clio.one/htopology/v1/fetch/?max=20&customPeers=\${BLOCKPRODUCING_IP}:\${BLOCKPRODUCING_PORT}:2|relays-new.cardano-mainnet.iohk.io:3001:2"EOF
Add permissions and pull new topology files.
###### On relaynode1###chmod +x relay-topology_pull.sh./relay-topology_pull.sh
The new topology takes after after restarting your stake pool.
###### On relaynode1###sudo systemctl restart cardano-node
Don't forget to restart your relay nodes after every time you fetch the topology!
Visit https://pooltool.io/
Create an account and login
Search for your stakepool id
Click ➡ Pool Details > Manage > CLAIM THIS POOL
Fill in your pool name and pool URL if you have one.
Fill in your Private Nodes and Your Relays as follows.
You can find your public IP with https://www.whatismyip.com/ or
curl http://ifconfig.me/ip
Add requests for nodes or "buddies" to each of your relay nodes. Make sure you include the IOHK node and your private nodes.
IOHK's node address is:
relays-new.cardano-mainnet.iohk.io
IOHK's node port is:
3001
For example, on relaynode1's buddies you should add requests for
your private BlockProducingNode
IOHK's node
and any other buddy/friendly nodes your can find or know
A relay node connection is not established until there is a request and an approval.
For relaynode1, create a get_buddies.sh script to update your topology.json file.
###### On relaynode1###cat > $NODE_HOME/get_buddies.sh << EOF#!/usr/bin/env bash# YOU CAN PASS THESE STRINGS AS ENVIRONMENTAL VARIABLES, OR EDIT THEM IN THE SCRIPT HEREif [ -z "\$PT_MY_POOL_ID" ]; then## CHANGE THESE TO SUIT YOUR POOL TO YOUR POOL ID AS ON THE EXPLORERPT_MY_POOL_ID="XXXXXXXX"fiif [ -z "\$PT_MY_API_KEY" ]; then## GET THIS FROM YOUR ACCOUNT PROFILE PAGE ON POOLTOOL WEBSITEPT_MY_API_KEY="XXXXXXXX"fiif [ -z "\$PT_MY_NODE_ID" ]; then## GET THIS FROM YOUR POOL MANAGE TAB ON POOLTOOL WEBSITEPT_MY_NODE_ID="XXXXXXXX"fiif [ -z "\$PT_TOPOLOGY_FILE" ]; then## SET THIS TO THE LOCATION OF YOUR TOPOLOGY FILE THAT YOUR NODE USESPT_TOPOLOGY_FILE="$NODE_HOME/${NODE_CONFIG}-topology.json"fiJSON="\$(jq -n --compact-output --arg MY_API_KEY "\$PT_MY_API_KEY" --arg MY_POOL_ID "\$PT_MY_POOL_ID" --arg MY_NODE_ID "\$PT_MY_NODE_ID" '{apiKey: \$MY_API_KEY, nodeId: \$MY_NODE_ID, poolId: \$MY_POOL_ID}')"echo "Packet Sent: \$JSON"RESPONSE="\$(curl -s -H "Accept: application/json" -H "Content-Type:application/json" -X POST --data "\$JSON" "https://api.pooltool.io/v0/getbuddies")"SUCCESS="\$(echo \$RESPONSE | jq '.success')"if [ \$SUCCESS ]; thenecho "Success"echo \$RESPONSE | jq '. | {Producers: .message}' > \$PT_TOPOLOGY_FILEecho "Topology saved to \$PT_TOPOLOGY_FILE. Note topology will only take effect next time you restart your node"elseecho "Failure "echo \$RESPONSE | jq '.message'fiEOF
For each of your relay nodes, update the following variables from pooltool.io into your get_buddies.sh file
PT_MY_POOL_ID
PT_MY_API_KEY
PT_MY_NODE_ID
Update your get_buddies.sh scripts with this information.
Use nano to edit your files.
nano $NODE_HOME/relaynode1/get_buddies.sh
Add execute permissions to these scripts. Run the scripts to update your topology files.
###### On relaynode1###cd $NODE_HOMEchmod +x get_buddies.sh./get_buddies.sh
Stop and then restart your stakepool in order for the new topology settings to take effect.
###### On relaynode1###sudo systemctl restart cardano-node
As your REQUESTS are approved, you must re-run the get_buddies.sh script to pull the latest topology data. Restart your relay nodes afterwards.
🔥 Critical step: In order to be a functional stake pool ready to mint blocks, you must see the Processed TX number increasing in gLiveView. If not, review your topology file and ensure your peers (or relay buddies) are well connected and ideally, minted some blocks.
🛑 Critical Key Security Reminder: The only stake pool keys and certs that are required to run a stake pool are those required by the block producer. Namely, the following three files.
###### On block producer node###KES=\${DIRECTORY}/kes.skeyVRF=\${DIRECTORY}/vrf.skeyCERT=\${DIRECTORY}/node.cert
All other keys must remain offline in your air-gapped offline cold environment.
🔥 Relay Node Security Reminder: Relay nodes must not contain any operational certifications
, vrf
, skey
or cold
keys.
Congratulations! Your stake pool is registered and ready to produce blocks.
After the epoch is over and assuming you successfully minted blocks, check with this:
cardano-cli query stake-address-info \--address $(cat stake.addr) \--allegra-era \--mainnet
Prometheus is a monitoring platform that collects metrics from monitored targets by scraping metrics HTTP endpoints on these targets. Official documentation is available here. Grafana is a dashboard used to visualize the collected data.
Install prometheus and prometheus node exporter.
sudo apt-get install -y prometheus prometheus-node-exporter
sudo apt-get install -y prometheus-node-exporter
Install grafana.
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" > grafana.listsudo mv grafana.list /etc/apt/sources.list.d/grafana.list
sudo apt-get update && sudo apt-get install -y grafana
Enable services so they start automatically.
sudo systemctl enable grafana-server.servicesudo systemctl enable prometheus.servicesudo systemctl enable prometheus-node-exporter.service
sudo systemctl enable prometheus-node-exporter.service
Update prometheus.yml located in /etc/prometheus/prometheus.yml
Change the <block producer public ip address> in the following command.
cat > prometheus.yml << EOFglobal:scrape_interval: 15s # By default, scrape targets every 15 seconds.# Attach these labels to any time series or alerts when communicating with# external systems (federation, remote storage, Alertmanager).external_labels:monitor: 'codelab-monitor'# A scrape configuration containing exactly one endpoint to scrape:# Here it's Prometheus itself.scrape_configs:# The job name is added as a label job=<job_name> to any timeseries scraped from this config.- job_name: 'prometheus'static_configs:- targets: ['localhost:9100']- targets: ['<block producer public ip address>:9100']- targets: ['<block producer public ip address>:12798']labels:alias: 'block-producer-node'type: 'cardano-node'- targets: ['localhost:12798']labels:alias: 'relaynode1'type: 'cardano-node'EOFsudo mv prometheus.yml /etc/prometheus/prometheus.yml
Finally, restart the services.
sudo systemctl restart grafana-server.servicesudo systemctl restart prometheus.servicesudo systemctl restart prometheus-node-exporter.service
Verify that the services are running properly:
sudo systemctl status grafana-server.service prometheus.service prometheus-node-exporter.service
Update ${NODE_CONFIG}-config.json
config files with new hasEKG
and hasPrometheus
ports.
cd $NODE_HOMEsed -i ${NODE_CONFIG}-config.json -e "s/127.0.0.1/0.0.0.0/g"
cd $NODE_HOMEsed -i ${NODE_CONFIG}-config.json -e "s/127.0.0.1/0.0.0.0/g"
Port forwarding and firewall config:
On block producer node (or relaynodeN), you need to open ports 12798 and 9100
On relaynode1, you will need to open ports 3000 for grafana.
Stop and restart your stake pool.
sudo systemctl restart cardano-node
sudo systemctl restart cardano-node
On relaynode1, open http://localhost:3000 or http://<your relaynode1 ip address>:3000 in your local browser. You may need to open up port 3000 in your router and/or firewall.
Login with admin / admin
Change password
Click the configuration gear icon, then Add data Source
Select Prometheus
Set Name to "Prometheus"
Set URL to http://localhost:9090