Hot tip: You can calculate your slot leader schedule, which tells you when it's your stake pools turn to mint a block. This can help you know what time is best to schedule maintenance on your stake pool. It can also help verify your pool is minting blocks correctly when it is your pool's turn. This is to be setup and run on the block producer node.
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 becomes available 1.5 days (36 hours) before the end of the current epoch.
Next epoch's leadership schedule is obtained with the following:
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
The automation of this process will work with the following method, as said, next epoch blocks can be checked 1.5 days before the start of the next epoch or at the 75% of the current epoch's completion.
What the script will do, is to calculate the correct day and hour to run the command, then wait until it is possible to do that and once the selected time comes, run the check listed below.
Once finished, it will redirect the output into a log file that can be analyzed.
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).
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 directoryDIRECTORY=if [[ !-d"$DIRECTORY/logs" ]]; thenmkdir $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 IDSTAKE_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.htmlMAGICNUMBER="1"# Edit variable with $TESTNET for Testnet and $MAINNET for Mainnetnetwork=$TESTNET# check for vrf.skey presenceif [[ !-f"$DIRECTORY/vrf.skey" ]]; thenecho"vrf.skey not found"; exit127; fiCCLI=$(whichcardano-cli)if [[ -z $CCLI ]]; thenecho"cardano-cli command cannot be found, exiting..."; exit127; fiJQ=$(whichjq)if [[ -z $JQ ]]; thenecho"jq command cannot be found, exiting..."; exit127; firead -ra BYRON_GENESIS <<< "$(jq -r '[ .startTime, .protocolConsts.k, .blockVersionData.slotDuration ] |@tsv' < $DIRECTORY/$network-byron-genesis.json)"
if [[ -z $BYRON_GENESIS ]]; thenecho"BYRON GENESIS config file not loaded correctly"; exit127; finetwork_magic=""if [ $network = $TESTNET ]; then network_magic="--testnet-magic $MAGICNUMBER"elif [ $network = $MAINNET ]; then network_magic="--mainnet"elseecho"Incorrect network selected, please use $TESTNET or $MAINNET network type"; exit1fi# Check that node is syncedfunctionisSynced(){ isSynced=false sync_progress=$($CCLI query tip $network_magic |jq-r".syncProgress")if [[ $sync_progress =="100.00" ]]; then isSynced=truefiecho $isSynced}# Get current epochfunctiongetCurrentEpoch(){echo $($CCLI query tip $network_magic |jq-r".epoch")}# Get epoch start time based on current onefunctiongetEpochStartTime(){ 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 onefunctiongetEpochEndTime(){#calculate currentEpoch Start time + 5 days of epoch duration - 10 minutes(600s) to not overlap with next epochecho $(( $(getEpochStartTime)+(5*86400)-(600) ))}# Get current timestampfunctiongetCurrentTime(){echo $(printf'%(%s)T\n'-1)}# Convert timestamps to UTC timefunctiontimestampToUTC(){ timestamp=$1echo $(date+"%D %T"-ud@$timestamp)}# Find the correct time to run the leaderslot check commandfunctiongetLeaderslotCheckTime(){ 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 executedfunctionsleepUntil(){ sleepSeconds=$1if [[ $sleepSeconds -gt0 ]]; thenecho"Script is going to sleep for: $sleepSeconds seconds"sleep $sleepSecondsfi}# Check leaderschedule of next epochfunctioncheckLeadershipSchedule(){ 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 ];thenecho"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" ]; thenecho"Check already done, check logs for results"; exit1elif [[ $timeDifference -gt86400 ]]; thenecho"Too early to run the script, wait for next cron scheduled job"; exit1elif [[ $timeDifference -gt0 ]] && [[ $timeDifference -le86400 ]]; thensleepUntil $timeDifferenceecho"Check is starting on $(timestampToUTC $(getCurrentTime))"checkLeadershipScheduleecho"Script ended, schedule logged inside file: leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt"elif [[ $timeDifference -lt0 ]] && [ !-f"$DIRECTORY/logs/leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt" ]; thenecho"Check is starting on $(timestampToUTC $(getCurrentTime))"checkLeadershipScheduleecho"Script ended, schedule logged inside file: leaderSchedule_$(( $(getCurrentEpoch)+1 )).txt"elseecho"There were problems on running the script, check that everything is working fine"; exit1fielseecho"Node not fully synced."; exit1fi
Set the following variables with your data:
# cardano node directory, directory where all files needed for running a cardano-node are locatedDIRECTORY=# Set your own stake pool IDSTAKE_POOL_ID=""# Set variable with $TESTNET for Testnet and $MAINNET for Mainnetnetwork=
Add execution permissions and test that the script is running without errors:
If 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:
To configure the job at the start of an epoch, keep in mind the following information:
Epoch in MAINNET starts at 21:45 UTC
Find the time when the cronjob should start:
Cronjobs run based on local timezone, not on UTC hours. \
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 useMAILTO=""# linux path, needed because cron doesn't know where to find cardano-cliPATH=# folder with cardano-node filesNODE_HOME=# testnet or mainnetNODE_CONFIG=# path to the socket of cardano node, should be under db/ folder under NODE_HOMECARDANO_NODE_SOCKET_PATH=MM HH * * * path_to_script/leaderScheduleCheck.sh > desired_log_folder/leaderSchedule_logs.txt 2>&1EOFcrontab-l|cat- ${NODE_HOME}/crontab-fragment.txt> ${NODE_HOME}/crontab.txt&&crontab ${NODE_HOME}/crontab.txtrm ${NODE_HOME}/crontab-fragment.txt
Once 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
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 } ]}EOF
Creating systemd Services
CNCLI sync and sendtip can be easily enabled as systemd services. When enabled as systemd services:
sync will continuously keep the cncli.db database synchronized.
sendtip will continuously send your stake pool tip to PoolTool.
To set up systemd:
Create the following and move to/etc/systemd/system/cncli-sync.service
Credits for inventing this process goes to the hard work by Andrew Westberg @amw7 (developer of JorManager and operator of BCSH family of stake pools).
If 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.
Your slot leader log should remain confidential. If you share this information publicly, an attacker could use this information to attack your stake pool.
Automate the process with Cronjob:
Installing the Binary
Running LeaderLog with stake-snapshot
Upgrading CNCLI
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.