Configuring Slot Leader Calculations
Since version 1.34, it is possible to check the slot leadership schedule for the current and next epoch using cardano-cli.
Next epoch's leadership schedule is obtained with the following:
cardano-cli query leadership-schedule \
--mainnet \
--genesis $NODE_HOME/shelley-genesis.json \
--stake-pool-id $(cat $NODE_HOME/stakepoolid.txt) \
--vrf-signing-key-file $NODE_HOME/vrf.skey \
--nextCurrent epoch's leadership schedule is obtained with the following:
cardano-cli query leadership-schedule \
--mainnet \
--genesis $NODE_HOME/shelley-genesis.json \
--stake-pool-id $(cat $NODE_HOME/stakepoolid.txt) \
--vrf-signing-key-file $NODE_HOME/vrf.skey \
--currentExample leadership schedule output:
SlotNo UTC Time
-------------------------------------------------------------
4073 2021-12-29 17:26:54.998001755 UTC
4126 2021-12-29 17:27:00.298001755 UTC
4206 2021-12-29 17:27:08.298001755 UTC
4256 2021-12-29 17:27:13.298001755 UTC
4309 2021-12-29 17:27:18.598001755 UTC
4376 2021-12-29 17:27:25.298001755 UTC
4423 2021-12-29 17:27:29.998001755 UTC
4433 2021-12-29 17:27:30.998001755 UTC🔁 Automate the process with Cronjob:
Keep in mind that running the leadership-schedule command, listed below and used by the script, with the cardano-node at the same time, will use approximately 17GB of RAM at the time of writing this guide (April 2022).
The possible solutions to avoid a node crash are:
Increase the RAM of the node
Create the leaderScheduleCheck.sh script file in the block producer (script can also be run on a relay node but vrf.skey needs to be exported there) and paste the following code inside of it:
#!/bin/bash
# cardano node directory
DIRECTORY=
if [[ ! -d "$DIRECTORY/logs" ]]; then mkdir $DIRECTORY/logs; fi
# create a pid, this way you can ps aux | grep leaderScheduleCheck.sh to see if script is running
echo $$ > "$DIRECTORY/logs/leaderScheduleCheck.pid";
# Set your own stake pool ID
STAKE_POOL_ID=""
TESTNET="testnet"
MAINNET="mainnet"
# Set the network magic value as needed for the testnet environment that you want to use
# For details on available testnet environments, see https://book.world.dev.cardano.org/environments.html
MAGICNUMBER="1"
# Edit variable with $TESTNET for Testnet and $MAINNET for Mainnet
network=$TESTNET
# check for vrf.skey presence
if [[ ! -f "$DIRECTORY/vrf.skey" ]]; then echo "vrf.skey not found"; exit 127; fi
CCLI=$(which cardano-cli)
if [[ -z $CCLI ]]; then echo "cardano-cli command cannot be found, exiting..."; exit 127; fi
JQ=$(which jq)
if [[ -z $JQ ]]; then echo "jq command cannot be found, exiting..."; exit 127; fi
read -ra BYRON_GENESIS <<< "$(jq -r '[ .startTime, .protocolConsts.k, .blockVersionData.slotDuration ] |@tsv' < $DIRECTORY/$network-byron-genesis.json)"
if [[ -z $BYRON_GENESIS ]]; then echo "BYRON GENESIS config file not loaded correctly"; exit 127; fi
network_magic=""
if [ $network = $TESTNET ]; then
network_magic="--testnet-magic $MAGICNUMBER"
elif [ $network = $MAINNET ]; then
network_magic="--mainnet"
else
echo "Incorrect network selected, please use $TESTNET or $MAINNET network type"; exit 1
fi
# Check that node is synced
function isSynced(){
isSynced=false
sync_progress=$($CCLI query tip $network_magic | jq -r ".syncProgress")
if [[ $sync_progress == "100.00" ]]; then
isSynced=true
fi
echo $isSynced
}
# Get current epoch
function getCurrentEpoch(){
echo $($CCLI query tip $network_magic | jq -r ".epoch")
}
# Get epoch start time based on current one
function getEpochStartTime(){
byron_genesis_start_time=${BYRON_GENESIS[0]}
byron_k=${BYRON_GENESIS[1]}
byron_epoch_length=$(( 10 * byron_k ))
byron_slot_length=${BYRON_GENESIS[2]}
echo $(( $byron_genesis_start_time + (($(getCurrentEpoch) * $byron_epoch_length * $byron_slot_length) / 1000) ))
}
# Get epoch end time based on the current one
function getEpochEndTime(){
#calculate currentEpoch Start time + 5 days of epoch duration - 10 minutes(600s) to not overlap with next epoch
echo $(( $(getEpochStartTime)+(5*86400)-(600) ))
}
# Get current timestamp
function getCurrentTime(){
echo $(printf '%(%s)T\n' -1)
}
# Convert timestamps to UTC time
function timestampToUTC(){
timestamp=$1
echo $(date +"%D %T" -ud @$timestamp)
}
# Find the correct time to run the leaderslot check command
function getLeaderslotCheckTime(){
epochStartTime=$(getEpochStartTime)
epochEndTime=$(getEpochEndTime)
# epoch completion percent to check for --next epoch leaderslots
percentage=75
checkTimestamp=$(( $epochStartTime+($percentage*($epochEndTime-$epochStartTime)/100) ))
echo $checkTimestamp
}
# Function to make the script sleep until check need to be executed
function sleepUntil(){
sleepSeconds=$1
if [[ $sleepSeconds -gt 0 ]]; then
echo "Script is going to sleep for: $sleepSeconds seconds"
sleep $sleepSeconds
fi
}
# Check leaderschedule of next epoch
function checkLeadershipSchedule(){
next_epoch=$(( $(getCurrentEpoch)+1 ))
currentTime=$(getCurrentTime)
echo "Check is running at: $(timestampToUTC $currentTime) for epoch: $next_epoch"
$CCLI query leadership-schedule $network_magic --genesis "$DIRECTORY/$network-shelley-genesis.json" --stake-pool-id $STAKE_POOL_ID --vrf-signing-key-file "$DIRECTORY/vrf.skey" --next > "$DIRECTORY/logs/leaderSchedule_$next_epoch.txt"
}
if [ isSynced ];then
echo "Current epoch: $(getCurrentEpoch)"
epochStartTimestamp=$(getEpochStartTime)
echo "Epoch start time: $(timestampToUTC $epochStartTimestamp)"
epochEndTimestamp=$(getEpochEndTime)
echo "Epoch end time: $(timestampToUTC $epochEndTimestamp)"
currentTime=$(getCurrentTime)
echo "Current cron execution time: $(timestampToUTC $currentTime)"
timestampCheckLeaders=$(getLeaderslotCheckTime)
echo "Next check time: $(timestampToUTC $timestampCheckLeaders)"
timeDifference=$(( $timestampCheckLeaders-$currentTime ))
if [ -f "$DIRECTORY/logs/leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt" ]; then
echo "Check already done, check logs for results"; exit 1
elif [[ $timeDifference -gt 86400 ]]; then
echo "Too early to run the script, wait for next cron scheduled job"; exit 1
elif [[ $timeDifference -gt 0 ]] && [[ $timeDifference -le 86400 ]]; then
sleepUntil $timeDifference
echo "Check is starting on $(timestampToUTC $(getCurrentTime))"
checkLeadershipSchedule
echo "Script ended, schedule logged inside file: leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt"
elif [[ $timeDifference -lt 0 ]] && [ ! -f "$DIRECTORY/logs/leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt" ]; then
echo "Check is starting on $(timestampToUTC $(getCurrentTime))"
checkLeadershipSchedule
echo "Script ended, schedule logged inside file: leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt"
else
echo "There were problems on running the script, check that everything is working fine"; exit 1
fi
else
echo "Node not fully synced."; exit 1
fiSet the following variables with your data:
# cardano node directory, directory where all files needed for running a cardano-node are located
DIRECTORY=
# Set your own stake pool ID
STAKE_POOL_ID=""
# Set variable with $TESTNET for Testnet and $MAINNET for Mainnet
network=Add execution permissions and test that the script is running without errors:
chmod +x leaderScheduleCheck.sh
./leaderScheduleCheck.shIf everything is working correctly, an output as the follow will be presented:
Current epoch: 199 Epoch start time: 04/14/22 20:20:16 Epoch end time: 04/19/22 20:10:16 Current cron execution time: 04/18/22 15:37:51 Next check time: 04/18/22 14:12:46 [...] Cutted output cause it can vary based on time when the script is ran
Configure Cronjob to make the script run automatically:
Find the time when the cronjob should start:
Find timezone:
timedatectl | grep "Time zone"
Once you found your timezone, you need to understand when run the job (It isn't mandatory to run it at epoch's starting hour). Here is an example with a UTC+2 timezone for Mainnet:
Epoch starting hour UTC: 21:45 Epoch starting hour for requested timezone: 23:45 Cronjob will be set to run at 23:45
Add cronjob and edit parameters based on your needs, PATH, NODE_HOME, NODE_CONFIG, CARDANO_NODE_SOCKET_PATH, MM, HH, path_to_script and desired_log_folder:
cat > $NODE_HOME/crontab-fragment.txt << EOF
# disable MTA use
MAILTO=""
# linux path, needed because cron doesn't know where to find cardano-cli
PATH=
# folder with cardano-node files
NODE_HOME=
# testnet or mainnet
NODE_CONFIG=
# path to the socket of cardano node, should be under db/ folder under NODE_HOME
CARDANO_NODE_SOCKET_PATH=
MM HH * * * path_to_script/leaderScheduleCheck.sh > desired_log_folder/leaderSchedule_logs.txt 2>&1
EOF
crontab -l | cat - ${NODE_HOME}/crontab-fragment.txt > ${NODE_HOME}/crontab.txt && crontab ${NODE_HOME}/crontab.txt
rm ${NODE_HOME}/crontab-fragment.txtOnce the cronjob is set, the script will be run every day and it will check if in the next 24H, it will be the correct time to run the command and see if there are scheduled blocks in the next epoch. For every epoch, there will be a file called leaderSchedule_epoch.txt
🧬 Installing the Binary
###
### On blockproducer
###
RELEASETAG=$(curl -s https://api.github.com/repos/cardano-community/cncli/releases/latest | jq -r .tag_name)
VERSION=$(echo ${RELEASETAG} | cut -c 2-)
echo "Installing release ${RELEASETAG}"
curl -sLJ https://github.com/cardano-community/cncli/releases/download/${RELEASETAG}/cncli-${VERSION}-ubuntu22-x86_64-unknown-linux-gnu.tar.gz -o /tmp/cncli-${VERSION}-ubuntu22-x86_64-unknown-linux-gnu.tar.gz
sudo tar xzvf /tmp/cncli-${VERSION}-ubuntu22-x86_64-unknown-linux-gnu.tar.gz -C /usr/local/bin/Confirming That CNCLI is Properly Installed
Run the following command to check if cncli is correctly installed and available in your system PATH variable:
command -v cncliIt should return /usr/local/bin/cncli
⛏️ Running LeaderLog with stake-snapshot
This command calculates a stake pool's expected slot list.
prevandcurrentlogs are available as long as you have a synchronized database.nextlogs are only available 1.5 days (36 hours) before the end of the epoch.You need to use
poolStakeMarkandactiveStakeMarkfornext,poolStakeSetandactiveStakeSetforcurrent,poolStakeGoandactiveStakeGoforprev.
Example usage with the stake-snapshot approach for next epoch:
/usr/local/bin/cncli sync --host 127.0.0.1 --port 6000 --no-service
MYPOOLID=$(cat $NODE_HOME/stakepoolid.txt)
echo "LeaderLog - POOLID $MYPOOLID"
SNAPSHOT=$(/usr/local/bin/cardano-cli query stake-snapshot --stake-pool-id $MYPOOLID --mainnet)
POOL_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "poolStakeMark": )\d+(?=,?)')
ACTIVE_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "activeStakeMark": )\d+(?=,?)')
MYPOOL=`/usr/local/bin/cncli leaderlog --consensus praos --pool-id $MYPOOLID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/byron-genesis.json --shelley-genesis ${NODE_HOME}/shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set next`
echo $MYPOOL | jq .
EPOCH=`echo $MYPOOL | jq .epoch`
echo "\`Epoch $EPOCH\` 🧙🔮:"
SLOTS=`echo $MYPOOL | jq .epochSlots`
IDEAL=`echo $MYPOOL | jq .epochSlotsIdeal`
PERFORMANCE=`echo $MYPOOL | jq .maxPerformance`
echo "\`MYPOOL - $SLOTS \`🎰\`, $PERFORMANCE% \`🍀max, \`$IDEAL\` 🧱ideal"Example usage with the stake-snapshot approach for current epoch:
/usr/local/bin/cncli sync --host 127.0.0.1 --port 6000 --no-service
MYPOOLID=$(cat $NODE_HOME/stakepoolid.txt)
echo "LeaderLog - POOLID $MYPOOLID"
SNAPSHOT=$(/usr/local/bin/cardano-cli query stake-snapshot --stake-pool-id $MYPOOLID --mainnet)
POOL_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "poolStakeSet": )\d+(?=,?)')
ACTIVE_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "activeStakeSet": )\d+(?=,?)')
MYPOOL=`/usr/local/bin/cncli leaderlog --consensus praos --pool-id $MYPOOLID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/byron-genesis.json --shelley-genesis ${NODE_HOME}/shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set current`
echo $MYPOOL | jq .
EPOCH=`echo $MYPOOL | jq .epoch`
echo "\`Epoch $EPOCH\` 🧙🔮:"
SLOTS=`echo $MYPOOL | jq .epochSlots`
IDEAL=`echo $MYPOOL | jq .epochSlotsIdeal`
PERFORMANCE=`echo $MYPOOL | jq .maxPerformance`
echo "\`MYPOOL - $SLOTS \`🎰\`, $PERFORMANCE% \`🍀max, \`$IDEAL\` 🧱ideal"Example usage with the stake-snapshot approach for previous epoch:
/usr/local/bin/cncli sync --host 127.0.0.1 --port 6000 --no-service
MYPOOLID=$(cat $NODE_HOME/stakepoolid.txt)
echo "LeaderLog - POOLID $MYPOOLID"
SNAPSHOT=$(/usr/local/bin/cardano-cli query stake-snapshot --stake-pool-id $MYPOOLID --mainnet)
POOL_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "poolStakeGo": )\d+(?=,?)')
ACTIVE_STAKE=$(echo "$SNAPSHOT" | grep -oP '(?<= "activeStakeGo": )\d+(?=,?)')
MYPOOL=`/usr/local/bin/cncli leaderlog --consensus praos --pool-id $MYPOOLID --pool-vrf-skey ${NODE_HOME}/vrf.skey --byron-genesis ${NODE_HOME}/byron-genesis.json --shelley-genesis ${NODE_HOME}/shelley-genesis.json --pool-stake $POOL_STAKE --active-stake $ACTIVE_STAKE --ledger-set prev`
echo $MYPOOL | jq .
EPOCH=`echo $MYPOOL | jq .epoch`
echo "\`Epoch $EPOCH\` 🧙🔮:"
SLOTS=`echo $MYPOOL | jq .epochSlots`
IDEAL=`echo $MYPOOL | jq .epochSlotsIdeal`
PERFORMANCE=`echo $MYPOOL | jq .maxPerformance`
echo "\`MYPOOL - $SLOTS \`🎰\`, $PERFORMANCE% \`🍀max, \`$IDEAL\` 🧱ideal"Integrating with PoolTool
PoolTool provides example scripts to submit the following data for your stake pool:
Current block height
The number of slots in which your stake pool is currently elected to mint blocks
The following figure shows the green badge that PoolTool displays next to your stake pool when your node is fully synchronized with the blockchain (image credit to QCPOL):

You can also use CNCLI utilities developed by the Cardano Community to send the block height and slot count to PoolTool.
Guild Operators maintain the cncli.sh companion script to help stake pool operators use the Cardano Community's CNCLI utilities.
To send data to PoolTool using CNCLI utilities without using the cncli.sh script, create a configuration file containing your PoolTool API key and stake pool details.
To create a configuration file, update values in the following example with your pool information. To follow the example, save the configuration file at $NODE_HOME/scripts/pooltool.json
cat > ${NODE_HOME}/scripts/pooltool.json << EOF
{
"api_key": "<UPDATE WITH YOUR API KEY FROM POOLTOOL PROFILE PAGE>",
"pools": [
{
"name": "<UPDATE TO MY POOL TICKER>",
"pool_id": "$(cat ${NODE_HOME}/stakepoolid.txt)",
"host" : "127.0.0.1",
"port": 6000
}
]
}
EOFCreating systemd Services
CNCLI sync and sendtip can be easily enabled as systemd services. When enabled as systemd services:
syncwill continuously keep thecncli.dbdatabase synchronized.sendtipwill continuously send your stake pooltipto PoolTool.
To set up systemd:
Create the following and move to
/etc/systemd/system/cncli-sync.service
cat > ${NODE_HOME}/cncli-sync.service << EOF
[Unit]
Description=CNCLI Sync
After=multi-user.target
[Service]
User=$USER
Type=simple
Restart=always
RestartSec=5
LimitNOFILE=131072
ExecStart=/usr/local/bin/cncli sync --host 127.0.0.1 --port 6000 --db ${NODE_HOME}/scripts/cncli.db
KillSignal=SIGINT
SuccessExitStatus=143
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cncli-sync
[Install]
WantedBy=multi-user.target
EOFsudo mv ${NODE_HOME}/cncli-sync.service /etc/systemd/system/cncli-sync.serviceCreate the following and move to
/etc/systemd/system/cncli-sendtip.service
cat > ${NODE_HOME}/cncli-sendtip.service << EOF
[Unit]
Description=CNCLI Sendtip
After=multi-user.target
[Service]
User=$USER
Type=simple
Restart=always
RestartSec=5
LimitNOFILE=131072
ExecStart=/usr/local/bin/cncli sendtip --cardano-node /usr/local/bin/cardano-node --config ${NODE_HOME}/scripts/pooltool.json
KillSignal=SIGINT
SuccessExitStatus=143
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cncli-sendtip
[Install]
WantedBy=multi-user.target
EOFsudo mv ${NODE_HOME}/cncli-sendtip.service /etc/systemd/system/cncli-sendtip.serviceTo enable and run the above services, run:
sudo systemctl daemon-reloadsudo systemctl start cncli-sync.servicesudo systemctl start cncli-sendtip.service🛠️ Upgrading CNCLI
RELEASETAG=$(curl -s https://api.github.com/repos/cardano-community/cncli/releases/latest | jq -r .tag_name)
VERSION=$(echo ${RELEASETAG} | cut -c 2-)
echo "Installing release ${RELEASETAG}"
curl -sLJ https://github.com/cardano-community/cncli/releases/download/${RELEASETAG}/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -o /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gzsudo tar xzvf /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -C /usr/local/bin/Confirming CNCLI Upgrades
cncli -VIt should return the updated version number.
Check if you have python installed.
python3 --versionOtherwise, install python3.
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install -y python3.9Check if you have pip installed.
pip3 --versionInstall pip3 if needed.
sudo apt-get install -y python3-pipInstall pytz which handles timezones.
pip3 install pytzVerify python and pip are setup correctly before continuing.
python3 --version
pip3 --versionClone the leaderLog scripts from papacarp/pooltool.io git repo.
cd $HOME/git
git clone https://github.com/papacarp/pooltool.io
cd pooltool.io/leaderLogsCalculate your slot leader schedule for the latest current epoch.
python3 leaderLogs.py \
--pool-id $(cat ${NODE_HOME}/stakepoolid.txt) \
--tz America/Los_Angeles \
--vrf-skey ${NODE_HOME}/vrf.skey🤖 Pro Tip: 1.5 days before the end of the current epoch, you can find the next epoch's schedule.
🤖 Pro Tip #2: Add the flag --epoch <INTEGER #> to find a specific epoch's slot schedule.
🤖 Pro Tip #3: Ensure your slot leader scripts are up to date.
cd $HOME/git/pooltool.io/leaderLogs
git pullIf your pool is scheduled to mint blocks, you should hopefully see output similar to this. Listed by date and time, this is your slot leader schedule or in other words, when your pool is eligible to mint a block.
Checking leadership log for Epoch 222 [ d Param: 0.6 ]
2020-10-01 00:11:10 ==> Leader for slot 121212, Cumulative epoch blocks: 1
2020-10-01 00:12:22 ==> Leader for slot 131313, Cumulative epoch blocks: 2
2020-10-01 00:19:55 ==> Leader for slot 161212, Cumulative epoch blocks: 3Your slot leader log should remain confidential. If you share this information publicly, an attacker could use this information to attack your stake pool.