{% hint style="info" %} You are required to regenerate the hot keys and issue a new operational certificate, a process called rotating the KES keys, when the hot keys expire.
Mainnet: KES keys will be valid for 120 rotations or 90 days {% endhint %}
When it's time to issue a new operational certificate, run the following to find the starting KES period.
{% tabs %} {% tab title="block producer node" %}
cd $NODE_HOME
slotNo=$(cardano-cli query tip --mainnet | jq -r '.slot')
slotsPerKESPeriod=$(cat $NODE_HOME/${NODE_CONFIG}-shelley-genesis.json | jq -r '.slotsPerKESPeriod')
kesPeriod=$((${slotNo} / ${slotsPerKESPeriod}))
startKesPeriod=${kesPeriod}
echo startKesPeriod: ${startKesPeriod}
{% endtab %} {% endtabs %}
Make a new KES key pair.
{% tabs %} {% tab title="block producer node" %}
cd $NODE_HOME
cardano-cli node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey
{% endtab %} {% endtabs %}
Copy kes.vkey to your cold environment.
Verify the current value of your node.counter is valid.
cat $HOME/cold-keys/node.counter
# Example where value is 6
# "Next certificate issue number: 6"
{% hint style="warning" %} A valid value of your node.counter MUST be greater than a recently created block's OpCertC value.
To find your pool's current **OpCertC **value, search for your pool on https://adapools.org/ and check the Blocks tab, then look at the **OpCertC **column.
For example, if your **OpCerC **value is 5, then your node.counter should be"Next certificate issue number: 6"
If not, then you need to increment the counter by running the below command with issue-op-cert. {% endhint %}
Create the new node.cert
file with the following command. Update <startKesPeriod>
with the value from above.
{% tabs %} {% tab title="air-gapped offline machine" %}
cd $NODE_HOME
chmod u+rwx $HOME/cold-keys
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
chmod a-rwx $HOME/cold-keys
{% endtab %} {% endtabs %}
{% hint style="danger" %} Copy node.cert back to your block producer node. {% endhint %}
Stop and restart your block producer node to complete this procedure.
{% tabs %} {% tab title="block producer node" %}
sudo systemctl restart cardano-node
{% endtab %}
{% tab title="manual" %}
cd $NODE_HOME
killall -s 2 cardano-node
./startBlockProducingNode.sh
{% endtab %} {% endtabs %}
{% hint style="info" %}
****:bulb: Best practice recommendation: It's now a good time to make a new backup of your new node.counter
file and cold-keys
directory to another USB drive or other offline location.
{% endhint %}
Want a clean start? Re-using existing server? Forked blockchain?
Delete git repo, and then rename your previous $NODE_HOME
and cold-keys
directory (or optionally, remove). Now you can start this guide from the beginning again.
rm -rf $HOME/git/cardano-node/ $HOME/git/libsodium/
mv $NODE_HOME $(basename $NODE_HOME)_backup_$(date -I)
mv $HOME/cold-keys $HOME/cold-keys_backup_$(date -I)
Corrupted or stuck blockchain? Delete all db folders.
cd $NODE_HOME
rm -rf db
{% hint style="danger" %} Important Reminder🔥 Any changes made in this section take effect in two epochs. A common mistake is lowering the pledge amount and removing funds too soon. This results in zero rewards as the current live pledge amount is no longer met. {% endhint %}
{% hint style="info" %} Need to change your pledge, fee, margin, pool IP/port, or metadata? Simply resubmit your stake pool registration certificate.
Reminder: There is no requirement to pay the 500 ADA stake pool deposit again. {% endhint %}
First, generate the protocol-parameters.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query protocol-parameters \
--mainnet \
--out-file $NODE_HOME/params.json
{% endtab %} {% endtabs %}
If you're changing your poolMetaData.json, remember to calculate the hash of your metadata file and re-upload the updated poolMetaData.json file. Refer to section 9 for information.
{% tabs %} {% tab title="block producer node" %}
cardano-cli stake-pool metadata-hash --pool-metadata-file poolMetaData.json > poolMetaDataHash.txt
{% endtab %} {% endtabs %}
If you changed your poolMetaData.json, copy poolMetaDataHash.txt to your cold environment.
Update the below registration-certificate transaction with your desired stake pool settings.
If you have **multiple relay nodes, **refer to section 12 and change your parameters appropriately.
{% hint style="warning" %} metadata-url must be no longer than 64 characters. {% endhint %}
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli stake-pool registration-certificate \
--cold-verification-key-file $HOME/cold-keys/node.vkey \
--vrf-verification-key-file vrf.vkey \
--pool-pledge 1000000000 \
--pool-cost 345000000 \
--pool-margin 0.20 \
--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
{% endtab %} {% endtabs %}
{% hint style="warning" %}
minPoolCost is 340000000 lovelace or 340 ADA. Therefore, your --pool-cost
must be at a minimum this amount.
{% endhint %}
{% hint style="info" %} Here we are pledging 1000 ADA with a fixed pool cost of 345 ADA and a pool margin of 20%. {% endhint %}
Copy pool.cert to your hot environment.
Pledge stake to your stake pool.
{% tabs %} {% tab title="air-gapped offline machine" %}
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
{% endtab %} {% endtabs %}
Copy deleg.cert to your hot environment.
You need to find the **tip **of the blockchain to set the **invalid-hereafter **parameter properly.
{% tabs %} {% tab title="block producer node" %}
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot')
echo Current Slot: $currentSlot
{% endtab %} {% endtabs %}
Find your balance and UTXOs.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet > fullUtxo.out
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out
cat balance.out
tx_in=""
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}")
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.out
txcnt=$(cat balance.out | wc -l)
echo Total ADA balance: ${total_balance}
echo Number of UTXOs: ${txcnt}
{% endtab %} {% endtabs %}
Run the build-raw transaction command.
{% hint style="info" %} The **invalid-hereafter **value must be greater than the current tip. In this example, we use current slot + 10000. {% endhint %}
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${total_balance} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee 0 \
--certificate-file pool.cert \
--certificate-file deleg.cert \
--out-file tx.tmp
{% endtab %} {% endtabs %}
Calculate the minimum fee:
{% tabs %} {% tab title="block producer node" %}
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
{% endtab %} {% endtabs %}
Calculate your change output.
{% tabs %} {% tab title="block producer node" %}
txOut=$((${total_balance}-${fee}))
echo txOut: ${txOut}
{% endtab %} {% endtabs %}
Build the transaction.
{% tabs %} {% tab title="block producer node" %}
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 \
--out-file tx.raw
{% endtab %} {% endtabs %}
Copy tx.raw to your cold environment.
Sign the transaction.
{% tabs %} {% tab title="air-gapped offline machine" %}
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
{% endtab %} {% endtabs %}
Copy tx.signed to your hot environment.
Send the transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet
{% endtab %} {% endtabs %}
Changes take effect in two epochs. After the next epoch transition, verify that your pool settings are correct.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query ledger-state --mainnet > ledger-state.json
jq -r '.esLState._delegationState._pstate._pParams."'"$(cat stakepoolid.txt)"'" // empty' ledger-state.json
{% endtab %} {% endtabs %}
Common use cases can include
- Downloading backups of stake/payment keys
- Uploading a new operational certificate to the block producer from an offline node
ssh <USERNAME>@<IP ADDRESS> -p <SSH-PORT>
rsync -avzhe “ssh -p <SSH-PORT>” <USERNAME>@<IP ADDRESS>:<PATH TO NODE DESTINATION> <PATH TO LOCAL PC DESTINATION>
Example:
ssh myusername@6.1.2.3 -p 12345
rsync -avzhe "ssh -p 12345" myusername@6.1.2.3:/home/myusername/cardano-my-node/stake.vkey ./stake.vkey
ssh <USERNAME>@<IP ADDRESS> -p <SSH-PORT>
rsync -avzhe “ssh -p <SSH-PORT>” <PATH TO LOCAL PC DESTINATION> <USERNAME>@<IP ADDRESS>:<PATH TO NODE DESTINATION>
Example:
ssh myusername@6.1.2.3 -p 12345
rsync -avzhe "ssh -p 12345" ./node.cert myusername@6.1.2.3:/home/myusername/cardano-my-node/node.cert
In order to defend against spoofing and hijacking of reputable stake pools, a owner can verify their ticker by proving ownership of an ITN stake pool.
{% hint style="info" %} Incentivized Testnet phase of Cardano’s Shelley era ran from late November 2019 to late June 2020. If you participated, you can verify your ticker. {% endhint %}
Make sure the ITN's jcli
binaries are present in $NODE_HOME
. Use jcli
to sign your stake pool id with your itn_owner.skey
{% tabs %} {% tab title="air-gapped offline machine" %}
./jcli key sign --secret-key itn_owner.skey stakepoolid.txt --output stakepoolid.sig
{% endtab %} {% endtabs %}
Visit pooltool.io and enter your owner public key and pool id witness data in the metadata section.
Find your pool id witness with the following command.
{% tabs %} {% tab title="air-gapped offline machine" %}
cat stakepoolid.sig
{% endtab %} {% endtabs %}
Find your owner public key in the file you generated on ITN. This data might be stored in a file ending in .pub
Finally, follow instructions to update your pool registration data with the pooltool generated metadata-url
and metadata-hash
. Notice the metadata has an "extended" field which proves your ticker ownership since ITN.
Keep your config files fresh by downloading the latest .json files.
cd $NODE_HOME
wget -N https://hydra.iohk.io/job/Cardano/cardano-node/cardano-deployment/latest-finished/download/1/${NODE_CONFIG}-config.json
wget -N https://hydra.iohk.io/job/Cardano/cardano-node/cardano-deployment/latest-finished/download/1/${NODE_CONFIG}-byron-genesis.json
wget -N https://hydra.iohk.io/job/Cardano/cardano-node/cardano-deployment/latest-finished/download/1/${NODE_CONFIG}-shelley-genesis.json
wget -N https://hydra.iohk.io/job/Cardano/cardano-node/cardano-deployment/latest-finished/download/1/${NODE_CONFIG}-alonzo-genesis.json
wget -N https://hydra.iohk.io/job/Cardano/cardano-node/cardano-deployment/latest-finished/download/1/${NODE_CONFIG}-topology.json
sed -i ${NODE_CONFIG}-config.json \
-e "s/TraceBlockFetchDecisions\": false/TraceBlockFetchDecisions\": true/g" \
-e "s/127.0.0.1/0.0.0.0/g"
Let's walk through an example to send 10 ADA to CoinCashew's tip address :upside_down:
{% hint style="info" %} The minimum amount, or smallest UTXO, you can send in one transaction is 1 ADA. {% endhint %}
First, find the **tip **of the blockchain to set the **invalid-hereafter **parameter properly.
{% tabs %} {% tab title="block producer node" %}
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot')
echo Current Slot: $currentSlot
{% endtab %} {% endtabs %}
Set the amount to send in lovelaces. ✨ Remember 1 ADA = 1,000,000 lovelaces.
{% tabs %} {% tab title="block producer node" %}
amountToSend=10000000
echo amountToSend: $amountToSend
{% endtab %} {% endtabs %}
Set the destination address which is where you're sending funds to.
{% tabs %} {% tab title="block producer node" %}
destinationAddress=addr1qxhazv2dp8yvqwyxxlt7n7ufwhw582uqtcn9llqak736ptfyf8d2zwjceymcq6l5gxht0nx9zwazvtvnn22sl84tgkyq7guw7q
echo destinationAddress: $destinationAddress
{% endtab %} {% endtabs %}
Find your balance and UTXOs.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet > fullUtxo.out
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out
cat balance.out
tx_in=""
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}")
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.out
txcnt=$(cat balance.out | wc -l)
echo Total ADA balance: ${total_balance}
echo Number of UTXOs: ${txcnt}
{% endtab %} {% endtabs %}
Run the build-raw transaction command.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+0 \
--tx-out ${destinationAddress}+0 \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee 0 \
--out-file tx.tmp
{% endtab %} {% endtabs %}
Calculate the current minimum fee:
{% tabs %} {% tab title="block producer node" %}
fee=$(cardano-cli transaction calculate-min-fee \
--tx-body-file tx.tmp \
--tx-in-count ${txcnt} \
--tx-out-count 2 \
--mainnet \
--witness-count 1 \
--byron-witness-count 0 \
--protocol-params-file params.json | awk '{ print $1 }')
echo fee: $fee
{% endtab %} {% endtabs %}
Calculate your change output.
{% tabs %} {% tab title="block producer node" %}
txOut=$((${total_balance}-${fee}-${amountToSend}))
echo Change Output: ${txOut}
{% endtab %} {% endtabs %}
Build your transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${txOut} \
--tx-out ${destinationAddress}+${amountToSend} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee ${fee} \
--out-file tx.raw
{% endtab %} {% endtabs %}
Copy tx.raw to your cold environment.
Sign the transaction with the payment secret key.
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction sign \
--tx-body-file tx.raw \
--signing-key-file payment.skey \
--mainnet \
--out-file tx.signed
{% endtab %} {% endtabs %}
Copy tx.signed to your hot environment.
Send the signed transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet
{% endtab %} {% endtabs %}
Check if the funds arrived.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address ${destinationAddress} \
--mainnet
{% endtab %} {% endtabs %}
You should see output similar to this showing the funds you sent.
TxHash TxIx Lovelace
----------------------------------------------------------------------------------------
100322a39d02c2ead.... 0 10000000
Do not skimp on this critical step to protect your pool and reputation.
{% content-ref url="how-to-harden-ubuntu-server.md" %} how-to-harden-ubuntu-server.md {% endcontent-ref %}
Let's walk through an example to claim your stake pools rewards.
{% hint style="info" %}
Rewards are accumulated in the stake.addr
address.
{% endhint %}
First, find the **tip **of the blockchain to set the **invalid-hereafter **parameter properly.
{% tabs %} {% tab title="block producer node" %}
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot')
echo Current Slot: $currentSlot
{% endtab %} {% endtabs %}
Set the amount to send in lovelaces. ✨ Remember 1 ADA = 1,000,000 lovelaces.
{% tabs %} {% tab title="block producer node" %}
rewardBalance=$(cardano-cli query stake-address-info \
--mainnet \
--address $(cat stake.addr) | jq -r ".[0].rewardAccountBalance")
echo rewardBalance: $rewardBalance
{% endtab %} {% endtabs %}
Set the destination address which is where you're moving your reward to. This address must have a positive balance to pay for transaction fees.
{% tabs %} {% tab title="block producer node" %}
destinationAddress=$(cat payment.addr)
echo destinationAddress: $destinationAddress
{% endtab %} {% endtabs %}
Find your payment.addr balance, utxos and build the withdrawal string.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet > fullUtxo.out
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out
cat balance.out
tx_in=""
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}")
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.out
txcnt=$(cat balance.out | wc -l)
echo Total ADA balance: ${total_balance}
echo Number of UTXOs: ${txcnt}
withdrawalString="$(cat stake.addr)+${rewardBalance}"
{% endtab %} {% endtabs %}
Run the build-raw transaction command.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+0 \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee 0 \
--withdrawal ${withdrawalString} \
--out-file tx.tmp
{% endtab %} {% endtabs %}
Calculate the current minimum fee:
{% tabs %} {% tab title="block producer node" %}
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
{% endtab %} {% endtabs %}
Calculate your change output.
{% tabs %} {% tab title="block producer node" %}
txOut=$((${total_balance}-${fee}+${rewardBalance}))
echo Change Output: ${txOut}
{% endtab %} {% endtabs %}
Build your transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${txOut} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee ${fee} \
--withdrawal ${withdrawalString} \
--out-file tx.raw
{% endtab %} {% endtabs %}
Copy tx.raw to your cold environment.
Sign the transaction with both the payment and stake secret keys.
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction sign \
--tx-body-file tx.raw \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--mainnet \
--out-file tx.signed
{% endtab %} {% endtabs %}
Copy tx.signed to your hot environment.
Send the signed transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction submit \
--tx-file tx.signed \
--mainnet
{% endtab %} {% endtabs %}
Check if the funds arrived.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address ${destinationAddress} \
--mainnet
{% endtab %} {% endtabs %}
You should see output similar to this showing your updated Lovelace balance with rewards.
TxHash TxIx Lovelace
----------------------------------------------------------------------------------------
100322a39d02c2ead....
{% hint style="info" %} :fire: 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. {% endhint %}
{% tabs %} {% tab title="CNCLI Tool" %} {% hint style="info" %}
A community-based cardano-node
CLI tool. It's a collection of utilities to enhance and extend beyond those available with the cardano-cli
.
{% endhint %}
###
### On blockproducer
###
RELEASETAG=$(curl -s https://api.github.com/repos/AndrewWestberg/cncli/releases/latest | jq -r .tag_name)
VERSION=$(echo ${RELEASETAG} | cut -c 2-)
echo "Installing release ${RELEASETAG}"
curl -sLJ https://github.com/AndrewWestberg/cncli/releases/download/${RELEASETAG}/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -o /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz
sudo tar xzvf /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -C /usr/local/bin/
Run the following command to check if cncli is correctly installed and available in your system PATH
variable:
command -v cncli
It should return /usr/local/bin/cncli
This command calculates a stake pool's expected slot list.
prev
andcurrent
logs are available as long as you have a synchronized database.next
logs are only available 1.5 days (36 hours) before the end of the epoch.- You need to use
poolStakeMark
andactiveStakeMark
fornext
,poolStakeSet
andactiveStakeSet
forcurrent
,poolStakeGo
andactiveStakeGo
forprev
.
Example usage with the stake-snapshot
approach for next
epoch:
{% hint style="info" %} Run this command 1.5 days (36 hours) before the next epoch begins. {% endhint %}
/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 --pool-id $MYPOOLID --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`
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 --pool-id $MYPOOLID --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`
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 --pool-id $MYPOOLID --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`
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"
CNCLI can send your tip and block slots to PoolTool. To do this, it requires that you set up a pooltool.json
file containing your PoolTool API key and stake pool details. Your PoolTool API key can be found on your pooltool profile page.
Here's an example pooltool.json
file.
Please update with your pool information and save it 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
CNCLI sync
and sendtip
can be easily enabled as systemd
services. When enabled as systemd
services:
sync
will continuously keep thecncli.db
database synchronized.sendtip
will continuously send your stake pooltip
to 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
EOF
sudo mv ${NODE_HOME}/cncli-sync.service /etc/systemd/system/cncli-sync.service
- Create 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
EOF
sudo mv ${NODE_HOME}/cncli-sendtip.service /etc/systemd/system/cncli-sendtip.service
- To enable and run the above services, run:
sudo systemctl daemon-reload
sudo systemctl start cncli-sync.service
sudo systemctl start cncli-sendtip.service
RELEASETAG=$(curl -s https://api.github.com/repos/AndrewWestberg/cncli/releases/latest | jq -r .tag_name)
VERSION=$(echo ${RELEASETAG} | cut -c 2-)
echo "Installing release ${RELEASETAG}"
curl -sLJ https://github.com/AndrewWestberg/cncli/releases/download/${RELEASETAG}/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -o /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz
sudo tar xzvf /tmp/cncli-${VERSION}-x86_64-unknown-linux-gnu.tar.gz -C /usr/local/bin/
cncli -V
It should return the updated version number. {% endtab %}
{% tab title="[Deprecated] Python Method" %} {% hint style="info" %} 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). {% endhint %}
Check if you have python installed.
python3 --version
Otherwise, 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.9
Check if you have pip installed.
pip3 --version
Install pip3 if needed.
sudo apt-get install -y python3-pip
Install pytz which handles timezones.
pip3 install pytz
Verify python and pip are setup correctly before continuing.
python3 --version
pip3 --version
Clone the leaderLog scripts from papacarp/pooltool.io git repo.
{% hint style="info" %} Official documentation for this LeaderLogs tool can be read here. {% endhint %}
cd $HOME/git
git clone https://github.com/papacarp/pooltool.io
cd pooltool.io/leaderLogs
Calculate 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
{% hint style="info" %} Set the timezone name to format the schedule's times properly. Use the --tz option. [Default: America/Los_Angeles]') Refer to the official documentation for more info. {% endhint %}
{% hint style="success" %} ****:robot: 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 pull
{% endhint %}
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.
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: 3
{% endtab %} {% endtabs %}
{% hint style="danger" %} Your slot leader log should remain confidential. If you share this information publicly, an attacker could use this information to attack your stake pool. {% endhint %}
{% hint style="info" %} Credits to QCPOL for this addition and credits to papacarp which this script is based on. Alternatively, use cncli's pooltool integration as described in section 18.12. {% endhint %}
When browsing pools on pooltool.io, you'll notice that there's a column named height
. It shows the node's current block and let your (future) delegators know that your node is running and up to date.
{% tabs %} {% tab title="block producer node" %} If your block producer doesn't have Internet access, you can use a relay node.
Installing the script
cd $NODE_HOME
wget https://cardano.stakepool.quebec/scripts/qcpolsendmytip.sh
sed -i -e 's/\r$//' qcpolsendmytip.sh
md5sum qcpolsendmytip.sh
To make sure the file is genuine, the md5 hash should be f7646132e922b24b140202e5f5cba3ac
. If it's not, stop here and delete the file with rm qcpolsendmytip.sh
.
You will need your pooltool.io API key (shown in your profile after registering).
sed -i qcpolsendmytip.sh -e "s|CFG_MY_POOL_ID|$(cat stakepoolid.txt)|"
sed -i qcpolsendmytip.sh -e "s/CFG_MY_API_KEY/<YOUR POOLTOOL API KEY HERE>/"
sed -i qcpolsendmytip.sh -e "s|CFG_MY_NODE_SOCKET_PATH|$NODE_HOME/db/socket|"
chmod +x qcpolsendmytip.sh
Installing the service (systemd)
cd $NODE_HOME
wget https://cardano.stakepool.quebec/services/qcpolsendmytip.service
sed -i -e 's/\r$//' qcpolsendmytip.service
md5sum qcpolsendmytip.service
To make sure the file is genuine, the md5 hash should be f848641fdc2692ee538e082bada44c2c
. If it's not, stop here and delete the file with rm qcpolsendmytip.service
.
sed -i qcpolsendmytip.service -e "s|CFG_WORKING_DIRECTORY|$NODE_HOME|g"
sed -i qcpolsendmytip.service -e "s|CFG_USER|$(whoami)|"
sudo mv qcpolsendmytip.service /etc/systemd/system/qcpolsendmytip.service
sudo chmod 644 /etc/systemd/system/qcpolsendmytip.service
sudo systemctl daemon-reload
sudo systemctl enable qcpolsendmytip
sudo systemctl start qcpolsendmytip
{% endtab %} {% endtabs %}
If everything was setup correctly, you should see your pool's height updated on pooltool.io.
{% hint style="warning" %} **Tip: **If the script uses too much CPU on your machine, you can lower the frequency it checks for new blocks. Simply change 0.5 in the following script by a value that works for you. The value is in seconds. The original value of the script is 0.1. {% endhint %}
cd $NODE_HOME
sed -i qcpolsendmytip.sh -e "s/sleep.*/sleep 0.5/"
Then restart the service:
sudo systemctl restart qcpolsendmytip
{% hint style="info" %} Secure your pool pledge account and pool reward account with a hardware wallet such as Trezor or Ledger Nano S/X. Credits to angelstakepool for documenting this process. {% endhint %}
{% hint style="danger" %} Critical Reminder: After adding a 2nd pool owner using a hardware wallet, you must wait 2 epochs before you transfer pledge funds from your CLI Method or **Mnemonic Method Wallet **to hardware wallet. Do not transfer any funds earlier because your pool pledge will be not met. {% endhint %}
First, delegate your 2nd pool owner to your stake pool with Daedalus or Yoroi or Adalite.io
Install cardano-hw-cli to interact with your hardware wallet.
{% tabs %} {% tab title="local PC or block producer node" %}
# Hardware Wallet works with Trezor and Ledger Nano S/X
# Reference https://github.com/vacuumlabs/cardano-hw-cli/blob/develop/docs/installation.md
cd $NODE_HOME
wget https://github.com/vacuumlabs/cardano-hw-cli/releases/download/v1.2.0/cardano-hw-cli_1.2.0-1.deb
sudo dpkg --install ./cardano-hw-cli_1.2.0-1.deb
{% endtab %} {% endtabs %}
Connect and unlock your hardware wallet on your local PC or block producer node.
Export your hardware wallet's staking keys.
{% tabs %} {% tab title="local PC or block producer node" %}
cardano-hw-cli address key-gen \
--path 1852H/1815H/0H/2/0 \
--verification-key-file hw-stake.vkey \
--hw-signing-file hw-stake.hwsfile
{% endtab %} {% endtabs %}
Copy hw-stake.vkey to your cold environment.
Update stake pool registration certificate to add your new hardware wallet owner, which will secure both your pool pledge account and pool reward account.
Tailor the below registration-certificate transaction with your pool's settings.
If you have **multiple relay nodes, **refer to section 12 and change your parameters appropriately.
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli stake-pool registration-certificate \
--cold-verification-key-file $HOME/cold-keys/node.vkey \
--vrf-verification-key-file vrf.vkey \
--pool-pledge 1000000000 \
--pool-cost 345000000 \
--pool-margin 0.10 \
--pool-reward-account-verification-key-file hw-stake.vkey \
--pool-owner-stake-verification-key-file stake.vkey \
--pool-owner-stake-verification-key-file hw-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
{% endtab %} {% endtabs %}
{% hint style="info" %} :eyes: Notice the pool-reward-account and additional pool-ownerstake-verification-key-file lines point to hw-stake.vkey.
Example above is pledging 1000 ADA with a fixed pool cost of 345 ADA and a pool margin of 10%. {% endhint %}
Copy pool.cert to your hot environment.
You need to find the **tip **of the blockchain to set the **invalid-hereafter **parameter properly.
{% tabs %} {% tab title="block producer node" %}
currentSlot=$(cardano-cli query tip --mainnet | jq -r '.slot')
echo Current Slot: $currentSlot
{% endtab %} {% endtabs %}
Find your balance and UTXOs.
{% tabs %} {% tab title="block producer node" %}
cardano-cli query utxo \
--address $(cat payment.addr) \
--mainnet > fullUtxo.out
tail -n +3 fullUtxo.out | sort -k3 -nr > balance.out
cat balance.out
tx_in=""
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}")
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.out
txcnt=$(cat balance.out | wc -l)
echo Total ADA balance: ${total_balance}
echo Number of UTXOs: ${txcnt}
{% endtab %} {% endtabs %}
Run the build-raw transaction command.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${total_balance} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee 0 \
--certificate-file pool.cert \
--out-file tx.tmp
{% endtab %} {% endtabs %}
Calculate the minimum fee:
{% tabs %} {% tab title="block producer node" %}
fee=$(cardano-cli transaction calculate-min-fee \
--tx-body-file tx.tmp \
--tx-in-count ${txcnt} \
--tx-out-count 1 \
--mainnet \
--witness-count 4 \
--byron-witness-count 0 \
--protocol-params-file params.json | awk '{ print $1 }')
echo fee: $fee
{% endtab %} {% endtabs %}
Calculate your change output.
{% tabs %} {% tab title="block producer node" %}
txOut=$((${total_balance}-${fee}))
echo txOut: ${txOut}
{% endtab %} {% endtabs %}
Build the transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction build-raw \
${tx_in} \
--tx-out $(cat payment.addr)+${txOut} \
--invalid-hereafter $(( ${currentSlot} + 10000)) \
--fee ${fee} \
--certificate-file pool.cert \
--out-file tx-pool.raw
{% endtab %} {% endtabs %}
Copy **tx-pool.raw **to your cold environment.
This multi signature transaction will be signed using witnesses.
You need the following 4 witnesses.
- node.skey
- hw-stake.hwsfile
- stake.skey
- payment**.**skey
Create a witness using node.skey,
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction witness \
--tx-body-file tx-pool.raw \
--signing-key-file $HOME/cold-keys/node.skey \
--mainnet \
--out-file node.witness
{% endtab %} {% endtabs %}
Create a witness using stake.skey,
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction witness \
--tx-body-file tx-pool.raw \
--signing-key-file stake.skey \
--mainnet \
--out-file stake.witness
{% endtab %} {% endtabs %}
Create a witness using payment.skey,
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction witness \
--tx-body-file tx-pool.raw \
--signing-key-file payment.skey \
--mainnet \
--out-file payment.witness
{% endtab %} {% endtabs %}
Copy tx-pool.raw to local PC or block producer node, which is where your hardware wallet device is connected. Ensure your hardware wallet is unlocked and ready.
Create a witness using hw-stake.hwsfile
{% tabs %} {% tab title="local PC or block producer node" %}
cardano-hw-cli transaction witness \
--tx-body-file tx-pool.raw \
--hw-signing-file hw-stake.hwsfile \
--mainnet \
--out-file hw-stake.witness
{% endtab %} {% endtabs %}
Copy **hw-stake.witness **to your cold environment.
{% tabs %} {% tab title="air-gapped offline machine" %}
cardano-cli transaction assemble \
--tx-body-file tx-pool.raw \
--witness-file node.witness \
--witness-file stake.witness \
--witness-file payment.witness \
--witness-file hw-stake.witness \
--out-file tx-pool.multisign
{% endtab %} {% endtabs %}
Copy tx-pool.multisign to your hot environment.
Send the transaction.
{% tabs %} {% tab title="block producer node" %}
cardano-cli transaction submit \
--tx-file tx-pool.multisign \
--mainnet
{% endtab %} {% endtabs %}
Check your updated pool information on adapools.org which should now show your hardware wallet as a pool owner.
{% hint style="danger" %} Important Reminder🔥 These changes take effect in two epochs. Do **not **transfer pledge funds to your hardware wallet until at least two epochs later. {% endhint %}
{% hint style="info" %} After two epoch snapshots have passed, you can safely transfer pledge funds from your CLI Method or Mnemonic Method wallet to your new hardware wallet owner account. 🚀 {% endhint %}
Here are the top problems a stake pool can experience and how to solve them.
- Pool configuration / metadata issues - Check with https://pool.vet If problems are detected, fix by updating your pool registration.
- Relay status - check your pool's relays on adapools.org under About Tab
- Block producer in/out connections - should match your environment. At least 1 in and 1 out connection is required. Check your firewall or IP/port configurations.
- TX processed count - must be non-zero on your block producer node. Check your network config.
- Time synchronization - install chrony on all BP/relay nodes.
- **Declared pledge is met **- check your pool on pooltool.io or adapools.org. Add more ADA to pledge address.