Skip to main content

Staking Service Guidelines

An important part of running a staking service is predicting/determining winning slots in which you can produce blocks, as well as paying out participants. The Mina protocol does not automatically payout rewards to delegates, so part of running a staking service is manually paying out participants.

This document aims to explain the different components that you should think about when managing those payouts. Specifically, this document provides an understanding of odds of winning blocks, gathering data from the ledger for later use, and computing relevant staking payout information from this data.

Staking Rewards

The coinbase reward for producing a block is 720 tokens. However, some accounts will receive 2x supercharged rewards if they contain only unlocked tokens. In the case of stake delegations, whether or not the reward is supercharged is based off of the account that won the block, not the account that is doing the staking and block production.

Dumping Staking Ledgers

In order to compute odds of winning a block for a given epoch, or to retroactively compute the coinbase reward a given account would receive, you need to have the staking ledger from that epoch. Mina daemons only keep around the staking ledger for the current epoch and the staking ledger for the next epoch, so if you want to capture a staking ledger for an epoch, you need to do it before or during that epoch.

The mina ledger export command can be used to export ledgers from a running daemon. It takes, as an argument, an identifier of the ledger you wish to export. The table below describes what each of these identifiers represent.


IdentifierDescription
staking-epoch-ledgerThe staking ledger for the current epoch.
next-epoch-ledgerThe staking ledger for the next epoch (epoch after current).
staged-ledgerThe most recent staged ledger (from the best tip of that node).

In order to ensure you always have each staking ledger available for use after epochs have expired, we recommend exporting the staking-epoch-ledger every 7140×360=357\dfrac{7140\times3}{60} = 357 hours (there are 71407140 slots in an epoch, and each slot is 33 minutes long).

By default, ledgers are exported as json data. See mina ledger export -help for documentation of flags which will enable other formats. When output as json, the ledger will be represented as an array of account objects. Below is an example of what an account object in json looks like.

{
"pk": "B62qrwZRsNkU39TrGpFwDdpRS2JaCB2yFZKMFNqLFYjcqGE5G5fWA8p",
"balance": "17000",
"delegate": "B62qrwZRsNkU39TrGpFwDdpRS2JaCB2yFZKMFNqLFYjcqGE5G5fWA8p",
"token": "1",
"token_permissions": {},
"receipt_chain_hash": "2mzbV7WevxLuchs2dAMY4vQBS6XttnCUF8Hvks4XNBQ5qiSGGBQe",
"voting_for": "3NK2tkzqqK5spR2sZ7tujjqPksL45M3UUrcA4WhCkeiPtnugyE2x",
"permissions": {
"stake": true,
"edit_state": "signature",
"send": "signature",
"set_delegate": "signature",
"set_permissions": "signature",
"set_verification_key": "signature"
}
}

For a running staking service, you are interested in the accounts which you control and the accounts which are staking to accounts you control. As an example, if we only had one account in our staking service, we could grab all the accounts we are interested in for a ledger using a command like:

mina ledger export staking-epoch-ledger | jq "$(cat <<EOF
.[] | \
select( \
.pk == "B62qjwvvHoM9RVt9onMpSMS5rFzhy3jjXY1Q9JU7HyHW4oWFEbWpghe" or \
.delegate == "B62qjwvvHoM9RVt9onMpSMS5rFzhy3jjXY1Q9JU7HyHW4oWFEbWpghe" \
)
EOF
)"

An important detail you will want to compute as a staking service is whether or not an account will receive supercharged coinbase rewards, which you will likely want to include into the staking service's weighting when paying out participants. Accounts with locked tokens (which are the accounts that do not receive supercharged staking rewards) will have a special "timing" field in their json representation. Here is an example of what this looks like:

{
"pk": "B62qmVHmj3mNhouDf1hyQFCSt3ATuttrxozMunxYMLctMvnk5y7nas1",
"balance": "230400",
"delegate": "B62qk2ujo9BoBxCs9BFQUsv3efaJDzbJeLs4YJdZMJzJoVj69ShVdKs",
"timing": {
"initial_minimum_balance": "230400",
"cliff_time": "86400",
"cliff_amount": "230400",
"vesting_period": "1",
"vesting_increment": "0"
}
}

Just because an account has a "timing" field, it does not necessarily mean that account has locked tokens. Timed accounts contain locked and unlocked tokens, and the "timing" field describes the unlock schedule for the locked tokens in that account. If an account has a "timing" field, you can compute the global slot at which that account's tokens will be fully unlocked, after which point the account will receive supercharged rewards. This can be computed by determining how long a timed account will take to fully vest it's tokens, and adding that to the "cliff_time", which is the slot where the account begins vesting tokens. Here is some example python code which will compute this unlock time from an account's timing information.

vesting_amount = initial_min_balance - cliff_amount
vesting_time = vesting_amount * math.ceil(vesting_period / vesting_increment)
unlocked_time = cliff_time + vesting_time

Choosing How to Payout Rewards

It's up to each staking service to decide how they wish to compute rewards. It's important to take account supercharging and the weights of different delegators into account, depending no how you choose to payout rewards.

As an example for calculating staking service payouts, we recommend taking a look at a document put together by a member of our community over at docs.minaexplorer.com.

Odds of Winning a Block

Blocks in Mina are produced within distinct time intervals called "slots". Each account in the ledger has a chance of winning each slot on the network, which allows them to produce a block for that slot (or, in the case of delegation, allows the delegated account to produce a block on their behalf). An account computes a random number (via a Verifiable Random Function, or VRF) for each slot, and compares that random number against a required threshold to determine if they have "won" that slot and can produce a block at that time. Their chance of winning a slot is thus determined by the threshold, which is a function of their relative stake in the network.

The stake distribution that is sampled when determining the VRF threshold are contained in a special ledger called the "staking ledger". Staking ledgers are fixed ledgers from past epochs in the network (epochs are fixed spans of slots; every epoch is 7140 slots long). Using past fixed ledgers prevents malicious stakers from increasing their chances of winning during an epoch by altering the distribution of stake on the main ledger (the "staged ledger"). Every epoch, the staking ledger changes. Due to this constraint, any account on the system can only check for "winning slots" within the next 2 epochs at any given time.

Once you know the staking ledger that will be used for a given epoch, the required VRF threshold for producing blocks in that epoch can be computed for each account. We can compute a stake ratio for a given account by dividing the stake that account controls in the staking ledger by the total amount of currency in the staking ledger. Figure 1 on page 20 of the Ouroboros Genesis paper provides the raw function for computing VRF thresholds. Filling in Mina's value for the active slots coefficient ff gives us the following function: ϕ(α)1(14)α\phi\left(\alpha\right) \triangleq 1 - \left(\dfrac{1}{4}\right)^\alpha. This function takes as a parameter α\alpha, which is the stake ratio we compute for an account.

Since each VRF is compared against this threshold (which is between 0 and 1), and each VRF is a pseudo-random number between 0 and 1, the VRF threshold for a given account is essentially the probability that a slot will be won by that account. Thus, we can extrapolate this function for computing VRF thresholds to compute the mean number of blocks we can expect a given account to win within an epoch. This can be done merely by summing all the probabilities for winning each slot of an epoch, and since the probability for an entire epoch is fixed, the mathematical expression to compute this simplifies to ϕ(α)×7140\phi\left(\alpha\right)\times7140.

As an example, let's say there is an account on the network which has 10610^6 (1 million) mina tokens in the staking ledger epoch epep. This same staking ledger for epoch epep has a total supply of 8×1088\times10^8 (800 million) mina tokens. We can compute the odds that this account will win an individual slot in epep using ϕ(1068×108)=0.0017313674\phi\left(\dfrac{10^6}{8\times10^8}\right) = 0.0017313674. This gives us a 0.17%\sim0.17\% chance that this account will win each slot during epoch epep. We can then compute the mean number of blocks we expect this account to produce for this epoch by multiplying the result by 71407140, giving us a probabilistic mean of 12.36\sim12.36 blocks for the epoch.

Sending Many Transactions

See Sending Many Transactions.