Maintenance Guide for Cardano Stake Pool    
✅ Updated for cardano-node 1.35.7
❤️ Big thanks to the excellent guide from CoinCashew, which part of this guide took reference from. I really learnt a lot from them.
To make it easy to follow and work smoothly, I had to work extremely hard to refine the details and test every single command. Any support is greatly appreciated 🙏 Thanks to your supports, I can continue updating this guide.
❤️ Stake with my pool, ticker: STAY
❤️ Donate to my address: addr1qyxd2rpa3fwxpj739zcddwac5knke4s2casn0czfr32v5zf6g5qmpfv40fal70xtuqc4wx4h708h26l78eajgmhuvntqhkhr4z
Update cardano-node
📒 You can check for the lastest release here. Click the "Watch" button to be notified on new releases.
  • Download cardano-node source
mkdir -p $HOME/src
cd $HOME/src
git clone https://github.com/input-output-hk/cardano-node.git
  • Checkout with the lastest cardano-node release.
cd $HOME/src/cardano-node
git fetch --all --recurse-submodules --tags
git stash
git checkout tags/1.35.7
  • Configure Cabal to use the correct GHC version.
cabal configure --with-compiler=ghc-8.10.7
🕚 It may take a while.
  • Update the local project file to use the correct VRF library.
echo "package cardano-crypto-praos" >>  cabal.project.local
echo "  flags: -external-libsodium-vrf" >>  cabal.project.local
  • Clean and update Cabal
cabal clean
cabal update
🕚 It may take a while.
  • Built Cardano node with cabal
cabal build all
🕚 It may take up to a few hours.
⚠️ Wait for the build to finish to continue.
  • Check the versions of cardano-node and cardano-cli that's just been built.
echo ;\
$(find $HOME/src/cardano-node/dist-newstyle/build -type f -name "cardano-node") version ;\
echo ;\
$(find $HOME/src/cardano-node/dist-newstyle/build -type f -name "cardano-cli") version
The versions should be 1.35.7
  • Stop running cardano-node now
sudo systemctl stop cardano-node
  • Install the newly built cardano-node and cardano-cli
mkdir -p ~/.local/bin
cp -p "$(./scripts/bin-path.sh cardano-node)" ~/.local/bin/
cp -p "$(./scripts/bin-path.sh cardano-cli)" ~/.local/bin/
Note, we avoid using cabal install because that method prevents the installed binaries from reporting the git revision with the --version switch.
  • Check the version that has been installed
echo ;\
cardano-cli --version ;\
echo ;\
cardano-node version
The versions should be 1.35.7
  • Update mainnet-config.json
cd $NODE_HOME
wget https://raw.githubusercontent.com/input-output-hk/cardano-node/master/configuration/cardano/mainnet-config.json
# Change "TraceBlockFetchDecisions" to "true" to enable live view.
sed -i mainnet-config.json \
    -e "s/TraceBlockFetchDecisions\": false/TraceBlockFetchDecisions\": true/g"
# Change Prometheus address so Monitoring relay node can scrape data from cardano-node.
sed -i $NODE_HOME/mainnet-config.json -e "s/127.0.0.1/0.0.0.0/g"  
  • Start the node now
sudo systemctl start cardano-node
or just type startnode
📒 When the node starts the first time after updating, it may perform database processing.

Update protocol parameters

cardano-cli query protocol-parameters \
    --mainnet \
    --out-file $NODE_HOME/params.json

Copy cardano-cli to Airgapped Computer

💻 On local computer
  • Download cardano-cli from any node to local computer
scp -r -P SSH_port -i /path/to/id_rsa username@ip_of_node:.local/bin/cardano-cli /path/to/Downloads/folder
💡 Tip: Drag and drop a file into terminal to get its path.
Example:
scp -r -P 1234 -i /Users/Charles/RSA/id_rsa charles@12.34.56.78:.local/bin/cardano-cli /Users/Charles/Downloads
  • Save the customized command to a text file to use when you update cardano-cli later.
  • Copy cardano-cli to a USB stick (It's best to format the USB first)
🔒 ON YOUR AIR-GAPPED COMPUTER
💡 You may want to save this web page as a HTML file and copy it to your 🔒 Airgapped Computer so you can copy and paste the commands.
  • Copy cardano-cli from USB stick to "cardano" folder.
🔒 ON YOUR AIR-GAPPED COMPUTER
  • Give execution permission
sudo chmod +x $HOME/cardano/cardano-cli
  • Check the version of cardano-cli
cardano-cli --version
The version should be 1.35.7
🔷 On Block-producing Node
  • (Optional) Check prequently-used parameters to see if their names are still the same.
This is optional because I will do it for you and update the whole guide every time there is a new cardano-node release.
echo ;\
echo ● stakePoolDeposit: $(cat $NODE_HOME/params.json | jq -r '.stakePoolDeposit') ;\
echo ● minPoolCost: $(cat $NODE_HOME/params.json | jq -r '.minPoolCost') ;\
echo ● stakeAddressDeposit: $(cat $NODE_HOME/params.json | jq -r '.stakeAddressDeposit') ;\
echo ● currentSlot: $(cardano-cli query tip --mainnet | jq -r '.slot') ;\
echo ● currentBlock: $(cardano-cli query tip --mainnet | jq -r '.block')
If any parameter returns null, that means its name has been changed. Check for the new name with these commands:
cat $NODE_HOME/params.json ;\
cardano-cli query tip --mainnet
Renew KES keys
📒 How to know when to renew KES keys
  • Option 1: Look at the "KES RENEWAL" panel on Grafana Dashboard.
  • Option 2: Check directly from cardano_node_metrics
🔷 On Block-producing Node
kes_remaining=$(curl -s localhost:12798/metrics | grep cardano_node_metrics_remainingKESPeriods_int | awk '{print $2}') ;\
slotsPerKESPeriod=$(cat $NODE_HOME/mainnet-shelley-genesis.json | jq -r '.slotsPerKESPeriod') ;\
echo ;\
echo ● Days left: $((${kes_remaining} * ${slotsPerKESPeriod} / 86400))
🔷 On Block-producing Node
  • Get the current pool-operation.cert counter number
🔴 This step is important because the counter number must increase after rotating KES keys. If not, your blocks will be invalid.
❤️ Thanks to CHRTY pool for this addition.
cardano-cli text-view decode-cbor --in-file $NODE_HOME/pool-operation.cert | grep int | head -1
Output should look like this
01  # int(0)
  • Take note of the int value, which is 0 in this example.

🔴 Important Housecleaning

🔷 On Block-producing Node
  • Rename old keys to prevent confusion with new keys
🔴 This step is important to make sure you don't mistakenly use your old KES keys. If you do, all your produced blocks will be invalid.
mv $NODE_HOME/kes.skey $NODE_HOME/kes-old-dont-use.skey ;\
mv $NODE_HOME/pool-operation.cert $NODE_HOME/pool-operation-old-dont-use.cert
  • Make sure the old keys have been renamed
cat $NODE_HOME/kes.skey ;\
cat $NODE_HOME/pool-operation.cert
Output should look like this
cat: /home/.../cardano/kes.skey: No such file or directory
cat: /home/.../cardano/pool-operation.cert: No such file or directory
🔒 ON YOUR AIR-GAPPED COMPUTER
  • Rename old keys to prevent confusion with new keys
🔴 This step is important to make sure you don't mistakenly use your old KES keys. If you do, all your produced blocks will be invalid.
mv $HOME/cardano/cold-keys/cold-kes.vkey $HOME/cardano/cold-keys/cold-kes-old-dont-use.vkey ;\
mv $HOME/cardano/kes.skey $HOME/cardano/kes-old-dont-use.skey ;\
mv $HOME/cardano/pool-operation.cert $HOME/cardano/pool-operation-old-dont-use.cert
  • Make sure the old keys have been renamed
cat $HOME/cardano/kes.skey ;\
cat $HOME/cardano/cold-keys/cold-kes.vkey ;\
cat $HOME/cardano/pool-operation.cert
Output should look like this
cat: /home/.../cardano/kes.skey: No such file or directory
cat: /home/.../cardano/cold-keys/cold-kes.vkey: No such file or directory
cat: /home/.../cardano/pool-operation.cert: No such file or directory
🔴 Also delete all kes.skey and pool-operation.cert files on your Local Computer and USB stick.

Generate New Keys

🔷 On Block-producing Node
  • Get current KES period
slotsPerKESPeriod=$(cat $NODE_HOME/mainnet-shelley-genesis.json | jq -r '.slotsPerKESPeriod') ;\
slotNo=$(cardano-cli query tip --mainnet | jq -r '.slot') ;\
kesPeriod=$((${slotNo} / ${slotsPerKESPeriod})) ;\
echo ;\
echo ● slotsPerKESPeriod: ${slotsPerKESPeriod} ;\
echo ● slotNo: ${slotNo} ;\
echo ● kesPeriod: ${kesPeriod}
  • Take note of the kesPeriod
🔒 On Air-gapped Computer
  • Create new KES key pair
cardano-cli node key-gen-KES \
  --verification-key-file $HOME/cardano/cold-keys/cold-kes.vkey \
  --signing-key-file $HOME/cardano/kes.skey
🔒 On Air-gapped Computer
Generate New Operational Certificate
  • Replace kesPeriod with the value taken earlier.
cardano-cli node issue-op-cert \
    --kes-verification-key-file $HOME/cardano/cold-keys/cold-kes.vkey \
    --cold-signing-key-file $HOME/cardano/cold-keys/cold-pool.skey \
    --operational-certificate-issue-counter $HOME/cardano/cold-keys/cold-op-cert-issue.counter \
    --kes-period kesPeriod \
    --out-file $HOME/cardano/pool-operation.cert
🔒 On Air-gapped Computer
  • Copy
  1. pool-operation.cert
  2. kes.skey
from cardano folder to your 💻 Local computer via a USB stick.
  • Upload them from 💻 Local computer to 🔷 Block-producing Node
🔷 On Block-producing Node
  • Check if files has been uploaded
ls -lh $NODE_HOME | grep 'pool-operation.cert\|kes.skey'
Output should contains
  1. pool-operation.cert
  2. kes.skey
🔴 The modification date of the two files must be today (in your server's timezone).
-rw-------  1 1.3K Mar 25 23:07 kes.skey
-rwxrwxr-x  1  365 Mar 25 23:40 pool-operation.cert
🔷 On Block-producing Node
  • Get the current pool-operation.cert counter number
cardano-cli text-view decode-cbor --in-file $NODE_HOME/pool-operation.cert | grep int | head -1
Output should look like this
01  # int(1)
🔴 The int number must be higher than the number before rotating taken earlier.
🔷 On Block-producing Node
  • Check read permission of files
ls $NODE_HOME -l
Output should look like this
-rw------- ....
  • Make sure you have at least read permission for every file. If not, cardano-node may not be able to produce block.
  • Go to cardanoscan.io, search for your pool and take note of the Vrf Hash registered on blockchain.
🔒 On Air-gapped Computer
  • Get the vrf hash of your vrf key.
cardano-cli node key-hash-VRF \
  --verification-key-file $HOME/cardano/cold-keys/cold-vrf.vkey
  • The two vrf hashes must be the same.
🔷 On Block-producing Node
  • Restart node for changes to take effect
sudo systemctl restart cardano-node
or just type restartnode
🕚 Wait a few minutes for cardano-node to start.
🔷 On Block-producing Node
  • Check KES days left again
kes_remaining=$(curl -s localhost:12798/metrics | grep cardano_node_metrics_remainingKESPeriods_int | awk '{print $2}') ;\
slotsPerKESPeriod=$(cat $NODE_HOME/mainnet-shelley-genesis.json | jq -r '.slotsPerKESPeriod') ;\
echo ;\
echo ● Days left: $((${kes_remaining} * ${slotsPerKESPeriod} / 86400))
Days left should be 93 days.
Add a New Relay
  • Follow all the steps in the CREATE page that relate to Relay node. After that, do the following additional steps:

Update firewall

🔷 On Block-producing Node
Allow your new Relay Node to access your Block-producing Node
  • Replace 1.2.3.4 with your new ✳️ Relay node's IPv4.
sudo ufw allow proto tcp from 1.2.3.4 to any port ${POOL_RELAY_PORT}
  • Check firewall status
sudo ufw status numbered
Output should contain this line
[ x] POOL_RELAY_PORT/tcp   ALLOW IN   NEW_RELAY_NODE_IP

Update Topology

🔷 On Block-producing Node
  • Edit topology
sudo nano $NODE_HOME/mainnet-topology.json
  • Add your new relay to the "Producers" list.
Your mainnet-topology.json may look like this:
{
    "Producers": [
        {
            "addr": "relay_1_IPv4",
            "port": ${POOL_RELAY_PORT},
            "valency": 1
        },
        {
            "addr": "relay_2_IPv4",
            "port": ${POOL_RELAY_PORT},
            "valency": 1
        }
    ]
}
  • Press Ctrl-X to exit.
  • Press Y then Enter/Return to confirm saving.
🔷 On Block-producing Node
  • Restart your node for changes to take effect.
sudo systemctl restart cardano-node
or just type restartnode

Update Prometheus

📊✳️ On Monitoring Relay Node
  • Edit prometheus.yml
sudo nano /etc/prometheus/prometheus.yml
  • Add new targets for your new relay node.
The file may look like this:
global:
  scrape_interval: 10s
  external_labels:
    monitor: 'codelab-monitor'
scrape_configs:
  # Scrape data from cardano-node
  - job_name: 'cardano-node'
    static_configs:
      - targets: ['localhost:12798']
      - targets: ['$(cat $NODE_HOME/bp-node-ip.txt):12798']
      - targets: ['new_relay_ip:12798']
      # Add more relay nodes here if needed
  # Scrape data from prometheus-node-exporter
  - job_name: 'node-exporter'
    static_configs:
      - targets: ['localhost:9100']
      - targets: ['$(cat $NODE_HOME/bp-node-ip.txt):9100']
      - targets: ['new_relay_ip:9100']
      # Add more relay nodes here if needed
📊✳️ On Monitoring Relay Node
  • Restart prometheus for changes to take effect
sudo systemctl restart prometheus
  • Check if the service is running
sudo systemctl status prometheus
Output should look like this
🟢 prometheus.service - Monitoring system and time series database
     Loaded: loaded (/lib/systemd/system/prometheus.service; enabled; vendor preset: enab>
     Active: active (running)
  • Press ctrl-C to exit

Resubmit Pool Registration If Necessary

Depending on the way you chose to register your relays, you may need to re-register your pool on the blockchain. Please refer to:
Change poolMetadata.json and get new hash
🔷 On Block-producing Node
🔴 Increase poolMetadata version count.
This version count will be attached to poolMetadata file name to create a new file every time you change pool metadata.
⚠️ If you only edit the old file and use the same URL for poolMetadata.json, the file content will be updated immediately while the registered hash may not be updated yet. That will cause a hash mismatch and may affect your pool's appearance on wallets and websites.
if [ ! -e $NODE_HOME/poolMetadata_VersionCount.txt ]
then
    echo "1" > $NODE_HOME/poolMetadata_VersionCount.txt
fi ;\
poolMetadataVersion=$(cat $NODE_HOME/poolMetadata_VersionCount.txt) ;\
poolMetadataVersion=$(($poolMetadataVersion + 1)) ;\
echo $poolMetadataVersion > $NODE_HOME/poolMetadata_VersionCount.txt ;\
echo ;\
echo ● Current Pool Metadata Version: $(tput bold)$poolMetadataVersion$(tput sgr0)
🔷 On Block-producing Node
  • Fill in the correct information to create poolMetadataVersionX.json
poolMetadataVersion=$(cat $NODE_HOME/poolMetadata_VersionCount.txt)
cat > $NODE_HOME/poolMetadataVersion${poolMetadataVersion}.json << EOF
{
"name": "Cool Pool name",
"description": "Here's why you should stake with us",
"ticker": "3-5 CHARACTERS",
"homepage": "https://www.examplepooldomain.com Just leave blank if don't use",
"extended": "https://link/to/poolExtendedMetadata.json"
}
EOF
echo ;\
echo ● File name: $(tput bold)poolMetadataVersion${poolMetadataVersion}.json$(tput sgr0) ;\
echo ● Content: ;\
cat $NODE_HOME/poolMetadataVersion${poolMetadataVersion}.json
💻 On local computer
  • Download poolMetadataVersionX.json to your 💻 Local computer.
  • Upload poolMetadataVersionX.json to your website or Github and get its URL. The URL must be HTTPS and no longer than 64 characters.
⚠️ If you choose to upload file to GitHub
See how to upload file to Github.
Because GitHub may modify the file content to optimize it, which in turn may change the hash of the file, you need to download it from GitHub to get the correct hash.
🔷 On Block-producing Node
⚠️ Only do this if you choose to upload file to GitHub
  • Download poolMetadataVersionX.json from GitHub, replacing git.io/abcde with the actual link.
poolMetadataVersion=$(cat $NODE_HOME/poolMetadata_VersionCount.txt) ;\
wget -O $NODE_HOME/poolMetadataVersion${poolMetadataVersion}.json https://git.io/abcde ;\
echo ;\
echo ● File name: $(tput bold)poolMetadataVersion${poolMetadataVersion}.json$(tput sgr0) ;\
echo ● Content: ;\
cat $NODE_HOME/poolMetadataVersion${poolMetadataVersion}.json
🔷 On Block-producing Node
  • Get the hash of poolMetadataVersionX.json
poolMetadataVersion=$(cat $NODE_HOME/poolMetadata_VersionCount.txt) ;\
cardano-cli stake-pool metadata-hash \
  --pool-metadata-file $NODE_HOME/poolMetadataVersion${poolMetadataVersion}.json > $NODE_HOME/poolMetadataHash.txt ;\
echo ;\
echo ● Hash is: $(cat $NODE_HOME/poolMetadataHash.txt)
📒 The commands above will validate the correctness of your JSON file before hashing it.
💻 On local computer
  • Download poolMetadataHash.txt to your 💻 Local computer.
  • Copy it to cardano folder on your 🔒 Air-gapped computer via a USB stick, replacing the old file.
  • Continue to the next section.
Update Pool Metadata, Pledge, Fee, Margin, Relays, Owner

Create New Pool Registration Certificate

🔷 On Block-producing Node
  • Rename pool-registration.cert to avoid confusion.
mv $NODE_HOME/pool-registration.cert $NODE_HOME/pool-registration-old.cert
🔒 On Air-gapped Computer
  • Specify the right values for the first 6 options
mv $HOME/cardano/pool-registration.cert $HOME/cardano/pool-registration-old.cert ;\
cardano-cli stake-pool registration-certificate \
  --pool-pledge YOUR_PLEDGE_IN_LOVELACE \
  --pool-cost 340000000 \
  --pool-margin 0.01 \
  --single-host-pool-relay relays.examplepooldomain.com \
  --pool-relay-port 6000 \
  --metadata-url https://link/to/poolMetadata.json \
  \
  \
  --metadata-hash $(cat $HOME/cardano/poolMetadataHash.txt) \
  --cold-verification-key-file $HOME/cardano/cold-keys/cold-pool.vkey \
  --vrf-verification-key-file $HOME/cardano/cold-keys/cold-vrf.vkey \
  --pool-reward-account-verification-key-file $HOME/cardano/cold-keys/cold-stake.vkey \
  --pool-owner-stake-verification-key-file $HOME/cardano/cold-keys/cold-stake.vkey \
  --mainnet \
  --out-file $HOME/cardano/pool-registration.cert ;\
echo ;\
echo ● pool-registration.cert content: ;\
cat $HOME/cardano/pool-registration.cert
pool-registration.cert should look like this
type: CertificateShelley
description: Stake Pool Registration Certificate
cborHex:
885e22d5d63...
  • Copy pool-registration.cert to 💻 Local computer and upload it to 🔷 Block-producing node.
🔷 On Block-producing Node
  • Get information for transaction. Copy all the command lines below and paste into terminal at the same time.
mv $NODE_HOME/tx.draft $NODE_HOME/tx-old.draft 2>/dev/null ;\
mv $NODE_HOME/tx.raw $NODE_HOME/tx-old.raw 2>/dev/null ;\
mv $NODE_HOME/tx.signed $NODE_HOME/tx-old.signed 2>/dev/null ;\
cd $NODE_HOME ;\
\
cardano-cli query utxo \
    --address $(cat $NODE_HOME/payment-with-stake.addr) \
    --mainnet > fullUtxo.out ;\
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out ;\
tx_in="" ;\
lovelace_total_balance=0 ;\
while read -r utxo; do
    in_addr=$(awk '{ print $1 }' <<< "${utxo}")
    idx=$(awk '{ print $2 }' <<< "${utxo}")
    utxo_balance=$(awk '{ print $3 }' <<< "${utxo}")
    lovelace_total_balance=$((${lovelace_total_balance}+${utxo_balance}))
    tx_in="${tx_in} --tx-in ${in_addr}#${idx}"
done < balance.out ;\
tx_in_count=$(cat balance.out | wc -l) ;\
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot') ;\
invalidHereafter=$((${currentSlot} + 10000)) ;\
\
echo ;\
echo ✅ VERIFY THE INFORMATION BELOW: ;\
echo ● UTxOs List: ; \
cat balance.out ; \
echo ● Total Lovelace balance: ${lovelace_total_balance} ;\
echo ● Number of UTxOs: ${tx_in_count} ;\
echo ● Transaction Input: ${tx_in} ;\
echo ● Current Slot: $currentSlot ;\
echo ● Transaction Invalid Hereafter: $invalidHereafter ;\
\
rm fullUtxo.out ;\
rm balance.out
🔷 On Block-producing Node
  • Build transaction draft
cardano-cli transaction build-raw \
  ${tx_in} \
  --tx-out $(cat $NODE_HOME/payment-with-stake.addr)+${lovelace_total_balance}  \
  --invalid-hereafter ${invalidHereafter} \
  --fee 0 \
  --certificate-file $NODE_HOME/pool-registration.cert \
  --certificate-file $NODE_HOME/delegation.cert \
  --out-file $NODE_HOME/tx.draft
🔷 On Block-producing Node
  • Calculate fee & transaction output
fee=$(cardano-cli transaction calculate-min-fee \
  --tx-body-file $NODE_HOME/tx.draft \
  --tx-in-count ${tx_in_count} \
  --tx-out-count 1 \
  --witness-count 3 \
  --byron-witness-count 0 \
  --mainnet \
  --protocol-params-file $NODE_HOME/params.json | awk '{ print $1 }') ;\
tx_out_change=$(($lovelace_total_balance - $fee)) ;\
tx_out_with_fee="$(cat $NODE_HOME/payment-with-stake.addr)+${tx_out_change}" ;\
echo ;\
echo ✅ VERIFY THE INFORMATION BELOW: ;\
echo ● fee: $fee ;\
echo ● Transaction Output Change: ${tx_out_change} ;\
echo ● Transaction Output WITH Fee: ${tx_out_with_fee}
🔷 On Block-producing Node
  • Build raw transaction
cardano-cli transaction build-raw \
  ${tx_in} \
  --tx-out ${tx_out_with_fee}  \
  --invalid-hereafter ${invalidHereafter} \
  --fee ${fee} \
  --certificate-file $NODE_HOME/pool-registration.cert \
  --certificate-file $NODE_HOME/delegation.cert \
  --out-file $NODE_HOME/tx.raw
  • Download tx.raw to 💻 Local computer and copy to 🔒 Air-gapped computer via a USB stick.
🔒 On Air-gapped Computer
  • Sign the transaction
mv $HOME/cardano/tx.signed $HOME/cardano/tx-old.signed ;\
cardano-cli transaction sign \
  --tx-body-file $HOME/cardano/tx.raw \
  --signing-key-file $HOME/cardano/cold-keys/cold-payment.skey \
  --signing-key-file $HOME/cardano/cold-keys/cold-pool.skey \
  --signing-key-file $HOME/cardano/cold-keys/cold-stake.skey \
  --mainnet \
  --out-file $HOME/cardano/tx.signed
  • Copy tx.signed to 💻 Local computer and upload it to 🔷 Block-producing node.
🔷 On Block-producing Node
  • Submit transaction
cardano-cli transaction submit \
--tx-file $NODE_HOME/tx.signed \
--mainnet
🔷 On Block-producing Node
  • Check if transaction is confirmed by blockchain.
cardano-cli query utxo --address $(cat $NODE_HOME/payment-with-stake.addr) --mainnet ;\
echo ● Expected total balance: ${tx_out_change}
The balance of payment-with-stake.addr should equal to Expected total balance. If not, wait a few minute for the transaction to be included in a block then recheck.
After the transaction has been processed, go to pool.vet and enter you pool's ticker to check if everything's ok. It may take a while for pool.vet to update your information.
Withdraw Your rewards
📒 All rewards will be sent to stake.addr
🔷 On Block-producing Node
  • Check your reward
reward_lovelace=$(cardano-cli query stake-address-info \
    --mainnet \
    --address $(cat $NODE_HOME/stake.addr) | jq -r ".[0].rewardAccountBalance") ;\
echo ;\
echo ● Rewards in lovelace: $(tput bold)$reward_lovelace$(tput sgr0)
🔷 On Block-producing Node
  • Set the reward recipient address.
Use the commands below to send rewards to payment-with-stake.addr or change $(cat payment-with-stake.addr) to any address you want. Just make sure the address is correct.
reward_recipient_address=$(cat payment-with-stake.addr) ;\
echo ;\
echo ● Reward recipient address: $(tput bold)$reward_recipient_address$(tput sgr0)
🔷 On Block-producing Node
  • Get information for transaction. Copy all the command lines below and paste into terminal at the same time.
mv $NODE_HOME/tx.draft $NODE_HOME/tx-old.draft 2>/dev/null ;\
mv $NODE_HOME/tx.raw $NODE_HOME/tx-old.raw 2>/dev/null ;\
mv $NODE_HOME/tx.signed $NODE_HOME/tx-old.signed 2>/dev/null ;\
cd $NODE_HOME ;\
\
cardano-cli query utxo \
    --address $reward_recipient_address \
    --mainnet > fullUtxo.out ;\
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out ;\
tx_in="" ;\
lovelace_total_balance=0 ;\
while read -r utxo; do
    in_addr=$(awk '{ print $1 }' <<< "${utxo}")
    idx=$(awk '{ print $2 }' <<< "${utxo}")
    utxo_balance=$(awk '{ print $3 }' <<< "${utxo}")
    lovelace_total_balance=$((${lovelace_total_balance}+${utxo_balance}))
    tx_in="${tx_in} --tx-in ${in_addr}#${idx}"
done < balance.out ;\
tx_in_count=$(cat balance.out | wc -l) ;\
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot') ;\
invalidHereafter=$((${currentSlot} + 10000)) ;\
withdrawal_input=$(cat $NODE_HOME/stake.addr)+${reward_lovelace} ;\
\
echo ;\
echo ✅ VERIFY THE INFORMATION BELOW: ;\
echo ● UTxOs List: ; \
cat balance.out ; \
echo ● Total Lovelace balance: ${lovelace_total_balance} ;\
echo ● Number of UTxOs: ${tx_in_count} ;\
echo ● Transaction Input: ${tx_in} ;\
echo ● Current Slot: $currentSlot ;\
echo ● Transaction Invalid Hereafter: $invalidHereafter ;\
echo ● Withdrawal Input: $withdrawal_input ;\
\
rm fullUtxo.out ;\
rm balance.out
🔷 On Block-producing Node
  • Build transaction draft
cardano-cli transaction build-raw \
  ${tx_in} \
  --tx-out ${reward_recipient_address}+0  \
  --invalid-hereafter ${invalidHereafter} \
  --fee 0 \
  --withdrawal ${withdrawal_input} \
  --out-file $NODE_HOME/tx.draft
🔷 On Block-producing Node
  • Calculate fee & transaction output
fee=$(cardano-cli transaction calculate-min-fee \
  --tx-body-file $NODE_HOME/tx.draft \
  --tx-in-count ${tx_in_count} \
  --tx-out-count 1 \
  --witness-count 2 \
  --byron-witness-count 0 \
  --mainnet \
  --protocol-params-file $NODE_HOME/params.json | awk '{ print $1 }') ;\
tx_out_change=$(($lovelace_total_balance - $fee + $reward_lovelace)) ;\
tx_out_with_fee="${reward_recipient_address}+${tx_out_change}" ;\
echo ;\
echo ✅ VERIFY THE INFORMATION BELOW: ;\
echo ● fee: $fee ;\
echo ● Transaction Output Change: ${tx_out_change} ;\
echo ● Transaction Output WITH Fee: ${tx_out_with_fee}
🔷 On Block-producing Node
  • Build raw transaction
cardano-cli transaction build-raw \
  ${tx_in} \
  --tx-out ${tx_out_with_fee} \
  --invalid-hereafter ${invalidHereafter} \
  --fee ${fee} \
  --withdrawal ${withdrawal_input} \
  --out-file $NODE_HOME/tx.raw
  • Download tx.raw to 💻 Local computer and copy to 🔒 Air-gapped computer via a USB stick.
🔒 On Air-gapped Computer
  • Sign the transaction
mv $HOME/cardano/tx.signed $HOME/cardano/tx-old.signed ;\
cardano-cli transaction sign \
  --tx-body-file $HOME/cardano/tx.raw \
  --signing-key-file $HOME/cardano/cold-keys/cold-payment.skey \
  --signing-key-file $HOME/cardano/cold-keys/cold-stake.skey \
  --mainnet \
  --out-file $HOME/cardano/tx.signed
  • Copy tx.signed to 💻 Local computer and upload it to 🔷 Block-producing node.
🔷 On Block-producing Node
  • Submit transaction
cardano-cli transaction submit \
--tx-file $NODE_HOME/tx.signed \
--mainnet
🔷 On Block-producing Node
  • Check if transaction is confirmed by blockchain.
cardano-cli query utxo --address ${reward_recipient_address} --mainnet ;\
echo ● Expected total balance: ${tx_out_change}
The balance of reward_recipient_address should equal to Expected total balance. If not, wait a few minute for the transaction to be included in a block then recheck.
Check Your Slot Leader Schedule
About 1.5 day before the end of the current epoch, you can check your slot leader schedule for the next epoch. This is useful to schedule your server maintainance.
⚠️ Your slot leader schedule is confidential. Do not expose it to the public. Bad actors may use that information to attack your pool.
❤️ Special thanks to Andrew Westberg (BCSH) for making this possible.
🔷 On Block-producing Node
  • Install cncli
CNCLI_RELEASE_TAG=$(curl -s https://api.github.com/repos/AndrewWestberg/cncli/releases/latest | jq -r .tag_name)
CNCLI_VERSION=$(echo ${CNCLI_RELEASE_TAG} | cut -c 2-)
curl -sLJ https://github.com/AndrewWestberg/cncli/releases/download/${CNCLI_RELEASE_TAG}/cncli-${CNCLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz -o /tmp/cncli-${CNCLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz
sudo tar xzvf /tmp/cncli-${CNCLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz -C /usr/local/bin/
🔷 On Block-producing Node
  • Check if cncli is properly installed.
command -v cncli
It should return /usr/local/bin/cncli
🔷 On Block-producing Node
  • Sync database. Replacing 0.0.0.0 with the IP of 🔷 Block-producing Node
/usr/local/bin/cncli sync --host 0.0.0.0 --port 6000 --no-service
Wait until the sync is 100% completed.
  • Take snapshot
POOL_ID=$(cat $NODE_HOME/pool-id.txt)
echo "POOL ID: $POOL_ID"
SNAPSHOT=$(cardano-cli query stake-snapshot --stake-pool-id $POOL_ID --mainnet)
🔷 On Block-producing Node
  • Get leader log for next epoch.
POOL_STAKE=$(jq .poolStakeMark <<< $SNAPSHOT)
ACTIVE_STAKE=$(jq .activeStakeMark <<< $SNAPSHOT)
/usr/local/bin/cncli leaderlog --pool-id $POOL_ID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/mainnet-byron-genesis.json --shelley-genesis ${NODE_HOME}/mainnet-shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set next
  • Get leader log for current epoch.
POOL_STAKE=$(jq .poolStakeSet <<< $SNAPSHOT)
ACTIVE_STAKE=$(jq .activeStakeSet <<< $SNAPSHOT)
/usr/local/bin/cncli leaderlog --pool-id $POOL_ID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/mainnet-byron-genesis.json --shelley-genesis ${NODE_HOME}/mainnet-shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set current
  • Get leader log for previous epoch.
POOL_STAKE=$(jq .poolStakeGo <<< $SNAPSHOT)
ACTIVE_STAKE=$(jq .activeStakeGo <<< $SNAPSHOT)
/usr/local/bin/cncli leaderlog --pool-id $POOL_ID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/mainnet-byron-genesis.json --shelley-genesis ${NODE_HOME}/mainnet-shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set prev
❤️ Donate to my address: addr1qyxd2rpa3fwxpj739zcddwac5knke4s2casn0czfr32v5zf6g5qmpfv40fal70xtuqc4wx4h708h26l78eajgmhuvntqhkhr4z