Merge PR #2990: Update f1 spec

* Update f1 spec:

* Improves exposition of the base scheme
* Fix inflation section / add sketch of how to do it
* Add future work section

I've also realized that using the ideas of the inflation section,
we could even remove the iteration over slashes when withdrawing!
(Note, inflation section equations / proof of correctness has not been
thoroughly transcribed from my head onto paper, so its not guaranteed to
be correct at the moment. However the inflation section of this describes
something separate from inflation in the hub. It has inflation happen by
having each staked token produce more staked tokens, instead of having it come
through block rewards. This inflation can also happen per validator,
so as to enable non-signing validators to not get inflation)

* Fix grammar

Co-Authored-By: ValarDragon <ValarDragon@users.noreply.github.com>

* Add proof of correctness to the inflation section

* Re-update PDF after merging chris' fixes

* address more of @cwgoes' suggestions
This commit is contained in:
Dev Ojha 2018-12-12 04:13:53 -08:00 committed by Christopher Goes
parent 0fbe319471
commit a31dc20e6a
2 changed files with 154 additions and 40 deletions

View File

@ -2,7 +2,7 @@
\usepackage{hyperref}
%opening
\title{F1 Fee Distribution Draft-00}
\title{F1 Fee Distribution Draft-01}
\author{Dev Ojha}
\begin{document}
@ -17,49 +17,71 @@
\section{F1 Fee Distribution}
In a proof of stake model, each validator has an associated stake, with delegators each contributing some amount to a validator's stake.
The validator is rewarded transaction fees every block for the service they are providing the network.
In the F1 distribution, each validator is permitted to take a commission from the fees they receive, and the remaining fees should be evenly distributed across the validator's delegators, such that every delegator the percentage of the validator's stake that came from the delegator is the proportion of that validator's remaining tx fees which they are getting.
Iterating over all delegators for every validator each block is too expensive for a blockchain environment.
Instead there is an explicit withdraw fees action which a delegator can take, which will give the delegator the same total amount of fees as though they were receiving it every block.
\subsection{Context}
In a proof of stake blockchain, each validator has an associated stake.
Transaction fees get rewarded to validators based on the incentive scheme of the underlying proof of stake model.
The fee distribution problem occurs in proof of stake blockchains supporting delegation, as there is a need to distribute a validator's fee rewards to its delegators.
The trivial solution of just giving the rewards to each delegator every block is too expensive to perform on-chain.
So instead fee distribution algorithms have delegators perform an explicit withdraw transaction, which when performed yields the same total amount of fees as if they had received them at every block.
Suppose a delegator delegates $x$ stake to a validator at block $h$.
This details F1, an approximation-free, slash-tolerant fee distribution algorithm which allows validator commission-rates, inflation rates, and fee proportions, which can all efficiently change per validator, every block.
The algorithm requires iterating over the validators every block, and withdraws require iterating over all of the corresponding validator's slashes whilst the delegator was bonded.
The former iteration is cheap, due to staking logic already requiring iteration over all validators, which causes the expensive state-reads to be cached.
The number of slashes is expected to be 0 or 1 for most validators,
so the latter term also meets the blockchain's efficiency needs.
The key point of how F1 works is that it tracks how much rewards a delegator with 1 stake for a given validator would be entitled to if it had bonded at block 0 until the latest block.
When a delegator bonds at block $b$, the amount of rewards a delegator with 1 stake would have if bonded at block 0 until block $b$ is also persisted to state.
When the delegator withdraws, they receive the difference of these two values.
Since rewards are distributed according to stake-weighting, this amount of rewards can be scaled by the amount of stake a delegator had.
Section 1.2 describes this in more detail, with an argument for it being approximation free.
Section 2 details how to adapt this algorithm to handle commission rates, slashing, and inflation.
\subsection{Base algorithm}
In this section, we show that the F1 base algorithm gives each delegator rewards identical to that which they'd receive in the naive and correct fee distribution algorithm that iterated over all delegators every block.
Even distribution of a validators rewards amongst its validators weighted by stake means the following:
Suppose a delegator delegates $x$ stake to a validator $v$ at block $h$.
Let the amount of stake the validator has at block $i$ be $s_i$ and the amount of fees they receive at this height be $f_i$.
Then if a delegator contributing $x$ stake decides to withdraw at block $n$, the rewards they receive is
Then if a delegator contributing $x$ stake decides to withdraw at block $n$, the rewards they receive are
$$\sum_{i = h}^{n} \frac{x}{s_i}f_i = x \sum_{i = h}^{n} \frac{f_i}{s_i}$$
However $s_i$ will not change every block.
It only changes if the validator gets slashed, or if someone new has bonded or unbonded.
Handling slashes is relegated to \autoref{ssec:slashing}.
Define a period as the set of blocks between two changes in a given validator's total stake.
Note that $s_i$ does not change every block,
it only changes if the validator gets slashed,
or if any delegator alters the amount they have delegated.
We'll relegate handling of slashes to \autoref{ssec:slashing},
and only consider the case with no slashing here.
We can change the iteration from being over every block, to instead being over the set of blocks between two changes in validator $v$'s total stake.
Let each of these set of blocks be called a period.
A new period begins every time that validator's total stake changes.
The above iteration will be converted to iteration over periods.
Let the total amount of stake for the validator in period $p$ be $n_p$.
Let $T_p$ be the total fees this validator accrued within this period.
Let $T_p$ be the total fees that validator $v$ accrued in period $p$.
Let $h$ be the start of period $p_{init}$, and height $n$ be the end of $p_{final}$.
It follows that
$$x \sum_{i = h}^{n} \frac{f_i}{s_i} = x \sum_{p = p_{init}}^{p_{final}} \frac{T_p}{n_p}$$
Let $p_0$ represent the period from when the validator first bonded until the first change to the validators stake.
The central idea to the F1 model is that at the end of the $k$th period, the following is stored at a state location indexable by $k$: $\sum_{i=0}^{k}\frac{T_i}{n_i}$.
When a delegator wants to delegate or withdraw their reward, they first create a new entry in state to end the current period. Let the index of the current period be $f$.
Then this entry is created using the previous entry as follows: $$\sum_{i=0}^{f}\frac{T_i}{n_i} = \sum_{i=0}^{f-1}\frac{T_i}{n_i} + \frac{T_f}{n_f} = entry_{f-1} + \frac{T_f}{n_f}$$
Let $p_0$ represent the period which begins when the validator first bonds.
The central idea to the F1 model is that at the end of the $k$th period,
the following is stored at a state location indexable by $k$: $\sum_{i=0}^{k}\frac{T_i}{n_i}$.
Let the index of the current period be $f$.
When a delegator wants to delegate or withdraw their reward, they first create a new entry in state to end the current period.
Then this entry is created using the previous entry as follows:
$$Entry_f = \sum_{i=0}^{f}\frac{T_i}{n_i} = \sum_{i=0}^{f-1}\frac{T_i}{n_i} + \frac{T_f}{n_f} = Entry_{f-1} + \frac{T_f}{n_f}$$
Where $T_f$ is the fees the validator has accrued in period $f$, and $n_f$ is the validators total amount of stake in period $f$.
The withdrawer's delegation object has the index $k$ for the period which they started accruing fees for.
Thus the reward they should receive when withdrawing is:
The withdrawer's delegation object has the index $k$ for the period which they ended by bonding. (They start receiving rewards for period $k + 1$)
The reward they should receive when withdrawing is:
$$x\left(entry_f - entry_k\right) = x\left(\left(\sum_{i=0}^{f}\frac{T_i}{n_i}\right) - \left(\sum_{i=0}^{k}\frac{T_i}{n_i}\right)\right) = x \sum_{i = k}^{f} \frac{T_i}{n_i}$$
$$x \sum_{i = k + 1}^{f} \frac{T_i}{n_i} = x\left(\left(\sum_{i=0}^{f}\frac{T_i}{n_i}\right) - \left(\sum_{i=0}^{k}\frac{T_i}{n_i}\right)\right) = x\left(Entry_f - Entry_k\right)$$
The first summation is the state entry for $f$, and the second sum is the state entry at $k$.
It is clear from the equations that this payout mechanism maintains correctness, and required no iterations.
It is clear from the equations that this payout mechanism maintains correctness, and requires no iterations. It just needed the two state reads for these entries.
$T_f$ is a separate variable in state for the amount of fees this validator has accrued since the last update to its power.
This variable is incremented at every block by however much fees this validator received that block.
On the update to the validators power, this variable is used to create the entry in state at $f$.
On the update to the validators power, this variable is used to create the entry in state at $f$, and is then reset to 0.
This fee distribution proposal is agnostic to how all of the blocks fees are divied up between validators.
This creates many nice properties, for example only rewarding validators who signed that block.
This creates many nice properties, for example it is possible to only rewarding validators who signed that block.
\section{Additional add-ons}
\subsection{Commission Rates}
@ -68,13 +90,14 @@ This can easily be done as follows:
In block $h$ a validator receives $f_h$ fees.
Instead of incrementing that validators ``total accrued fees this period variable" by $f_h$, it is instead incremented by $(1 - commission\_rate) * f_p$.
Then $commission\_rate * f_p$ is deposited directly to the validator.
This scheme allow for updates to a validator's commission rate every block if desired.
Then $commission\_rate * f_p$ is deposited directly to the validator's account.
This allows for efficient updates to a validator's commission rate every block if desired.
More generally, each validator could have a function which takes their fees as input, and outputs a set of outputs to pay these fees too. (i.e. x\% going to themselves, y\% to delegators, z\% burnt)
\subsection{Slashing}
\label{ssec:slashing}
Slashing is distinct from withdrawals, since not only does it lower the validators total amount of stake, but it also lowers each of its delegator's stake by a fixed percentage.
Since noone is charged gas for slashes, a slash cannot iterate over all delegators.
Slashing is distinct from withdrawals, since it lowers the stake of all of the delegator's by a fixed percentage.
Since no one is charged gas for slashes, a slash cannot iterate over all delegators.
Thus we can no longer just multiply by $x$ over the difference in stake.
The solution here is to instead store each period created by a slash in the validators state.
Then when withdrawing, you must iterate over all slashes between when you started and ended.
@ -85,24 +108,94 @@ When there are multiple slashes, you just account for the accumulated slash fact
In practice this will not really be an efficiency hit, as we can expect most validators to have no slashes.
Validators that get slashed a lot will naturally lose their delegators.
A malicious validator that gets itself slashed many times would increase the gas to withdraw linearly, but the economic loss of funds due to the slashes should far out-weigh the extra overhead the withdrawer must pay for due to the gas.
A malicious validator that gets itself slashed many times would increase the gas to withdraw linearly, but the economic loss of funds due to the slashes should far out-weigh the extra overhead the honest withdrawer must pay for due to the gas.
\subsection{Inflation}
Inflation is the idea that we want every staked coin to grow in value as time progresses. Each block, every staked token should each be rewarded $x$ staking tokens as inflation, where $x$ is calculated from function which takes state and the block information as input. This also allows for many seemless upgrade's to $x$'s algorithm. This can be added efficiently into the fee distribution model as follows:
Inflation is the idea that we want every staked coin to create more staking tokens as time progresses.
The purpose being to drive down the relative worth of unstaked tokens.
Each block, every staked token should produce $x$ staking tokens as inflation, where $x$ is calculated from a function $inflation$ which takes state and the block information as input.
Let $x_i$ represent the evaluation of $inflation$ in the $i$th block.
The goal of this section is to auto-bond inflation in the fee distribution model without iteration.
This is done by preserving the invariant that every state entry contains the rewards one would have if they had bonded one stake at genesis until that corresponding block.
Make each block have an inflation number, by which every staked token should produce $x$ additional staking tokens. In state there is a variable for the sum of all such inflation numbers. Then each period will store this total inflation sum in addition to $\sum_{i=0}^{end}\frac{T_i}{n_i}$. When withdrawing perform a subtraction on the inflation sums at the end and at the start to see how many new staking tokens to produce per staked token.
In state a variable should be kept for the number of tokens one would have now due to inflation,
given that they bonded one token at genesis.
This is $\prod_{0}^{now} (1 + x_i)$.
Each period now stores this total inflation product along with what it already stores per-period.
This works great in the model where the inflation rate should be dynamic each block, but apply the same to each validator. Inflation creation can trivially be epoched as long as inflation isn't required within the epoch, through changes to the $x$ function.
Let $R_i$ be the fee rewards in block $i$, and $n_i$ be the total amount bonded to that validator in that block.
The correct amount of rewards which 1 token at genesis should have now is:
$$Reward(now) = \sum_{i = 0}^{now}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i}$$
The term in the sum is the amount of stake one stake becomes due to inflation, multiplied by the amount of fees per stake.
Note that this process is extremely efficient.
Now we cast this into the period frame of view.
Recall that we build the rewards by creating a state entry for the rewards of the previous period, and keeping track of the rewards within this period.
Thus we first define the correct amount of rewards for each successive period, proving correctness of this via induction.
We then show that the state entry that gets efficiently built up block by block is equal to this value for the latest period.
The above can be trivially amended if we want inflation to proceed differently for different validators each block. (e.g. depending on who was offline) It can also be made to be easily adapted in a live chain. It is unclear if either of these two are more desirable settings.
Let $start, end$ denote the start/end of a period.
Suppose that $\forall f > 0$, $Reward(end(f))$ is correctly constructed as
$$Reward(end(f)) = Reward(end(f-1)) + \sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$
and that for $f = 0$, $Reward(end(0)) = 0$.
(With period 1 being defined as the period that has the first bond into it)
It must be shown that assuming the supposition $\forall f \leq f_0$, $$Reward(end(f_0 + 1)) = Reward(end(f_0)) + \sum_{i = start(f_0 + 1)}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$
Using the definition of $Reward$, it follows that:
$$\sum_{i = 0}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i} = \sum_{i = 0}^{end(f_0)}\left(\prod_{j = 0}^{i} 1 + x_j \right) * \frac{R_i}{n_i} + \sum_{i = start(f_0 + 1)}^{end(f_0 + 1)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$
Since the first summation on the right hand side is $Reward(end(f_0))$, the supposition is proven true.
Consequently, the reward for just period $f$ adjusted for the amount of inflation 1 token at genesis would produce, is:
$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$
TODO: make this proof + pre-amble less verbose, and just wrap up into a lemma.
Maybe just leave this proof or the last part to the reader, since it easily follows from summation bounds.
Now note that
$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i} = \left(\prod_{j = 0}^{end(f - 1)} 1 + x_j \right)\sum_{i = start(f)}^{end(f)}\left(\prod_{j = start(f)}^{i} 1 + x_j \right) \frac{R_i}{n_i}$$
By definition of period, and inflation being applied every block, \\
$n_i = n_{start(f)}\left(\prod_{j = start(f)}^{i} 1 + x_j \right)$. This cancels out the product in the summation, therefore
$$\sum_{i = start(f)}^{end(f)}\left(\prod_{j = 0}^{i} 1 + x_j \right) \frac{R_i}{n_i} = \left(\prod_{j = 0}^{end(f - 1)} 1 + x_j \right)\frac{\sum_{i = start(f)}^{end(f)}R_i}{n_{start(f)}}$$
Thus every block, each validator just has to add the total amount of fees (The $R_i$ term) that goes to delegates to some per-period term.
When creating a new period, $n_{start(f)}$ can be cached in state, and the product is already stored in the previous periods state entry.
You then get the next period's $n_{start(f)}$ from the current tm-power entry.
This is thus extremely efficient per block.
When withdrawing, you take the difference as before,
which yields the amount of rewards you would have obtained with $(\prod_0^{begin\ bonding\ period}1 + x)$ stake from the block you began bonding at until now.
$(\prod_0^{begin\ bonding\ period}1 + x)$ is known, since its included in the state entry for when you bonded.
You then divide the entitled fees by $(\prod_0^{begin\ bonding\ period}1 + x)$ to normalize it to being the amount of rewards you're entitled to from 1 stake at that block to now.
Then as before, you multiply by the amount of stake you had initially bonded.
TODO: (Does the difference equating to that make sense, or should it be shown explicitly)
Note that the inflation function could vary per block,
and per validator if ever a need rose.
If the inflation rate is the same for everyone then there can be a single global store for the entries corresponding to the product of inflations.
Inflation creation can trivially be epoched as long as inflation isn't required within the epoch, through changes to the $inflation$ function.
Again note that this process is extremely efficient.
\subsection{Withdrawing with no iteration over slashes}
TODO: Fill this out.
Core idea: you use the same mechanism as previously, but you just make that blocks $x_j$ term negative.
(So a $20\%$ slash would be equivalent to an inflation on that validator of $-20\%$)
This foregoes the constant inflation per validator, may or may not be worth it depending on expected number of slashes
\subsection{Auto bonding fees}
TODO: Fill this out.
Core idea: you use the same mechanism as previously, but you just don't take that optimization with $n_{i}$ and the $n_{start}$ relation.
Fairly simple to do.
\subsection{Delegation updates}
Updating your delegation amount is equivalent to withdrawing earned rewards and a fully independent new delegation occuring in the same block.
Updating your delegation amount is equivalent to withdrawing earned rewards and a fully independent new delegation occurring in the same block.
The same applies for redelegation.
From the view of fee distribution, partial redelegation is the same as a delegation update + a new delegation.
\subsection{Jailing / being kicked out of the validator set}
This basically requires no change. In each block you only iterate over the currently bonded validators. So you simply don't update the "total accrued fees this period" variable for jailed / non-bonded validators. Withdrawing requires \textit{no} special casing here!
This basically requires no change.
In each block you only iterate over the currently bonded validators.
So you simply don't update the "total accrued fees this period" variable for jailed / non-bonded validators.
Withdrawing requires \textit{no} special casing here!
\section{State pruning}
You will notice that in the main scheme there was no note for pruning entries from state.
@ -110,11 +203,20 @@ We can in fact prune quite effectively.
Suppose for the sake of exposition that there is at most one delegation / withdrawal to a particular validator in any given block.
Then each delegation is responsible for one addition to state.
Only the next period, and this delegator's withdrawal could depend on this entry. Thus once this delegator withdraws, this state entry can be pruned.
For the entry created by the delegator's withdrawal, that is only required by the creation of the next period. Thus once the next period is created, that withdrawal's period can be deleted.
For the entry created by the delegator's withdrawal, that is only required by the creation of the next period.
Thus once the next period is created, that withdrawal's period can be deleted.
This can be easily adapted to the case where there are multiple delegations / withdrawals per block. Keep a counter per state entry for how many delegations need to be cleared. (So 1 for each delegation in that block which created that period, 0 for each withdrawal) When creating a new period, check that the previous period (which had to be read anyway) doesn't have a count of 0. If it does have a count of 0, delete it. When withdrawing, decrement the period which created this delegation's counter by 1. If that counter is now 0, delete that period.
This can be easily adapted to the case where there are multiple delegations / withdrawals per block.
Keep a counter per state entry for how many delegations need to be cleared.
(So 1 for each delegation in that block which created that period, 0 for each withdrawal)
When creating a new period, check that the previous period (which had to be read anyway) doesn't have a count of 0.
If it does have a count of 0, delete it.
When withdrawing, decrement the period which created this delegation's counter by 1.
If that counter is now 0, delete that period.
The slash entries for a validator can only be pruned when all of that validator's delegators have their bonding period starting after the slash. This seems ineffective to keep track of, thus it is not worth it. Each slash should instead remain in state until the validator unbonds and all delegators have their fees withdrawn.
The slash entries for a validator can only be pruned when all of that validator's delegators have their bonding period starting after the slash.
This seems ineffective to keep track of, thus it is not worth it.
Each slash should instead remain in state until the validator unbonds and all delegators have their fees withdrawn.
\section{Implementers Considerations}
@ -132,4 +234,16 @@ This is an extremely simple scheme with many nice benefits.
Thus this scheme has efficiency improvements, simplicity improvements, and expressiveness improvements over the currently proposed schemes. With a correct fee distribution amongst the validator set, this solves the existing problem where one could withhold their signature for risk-free gain.
\section{TO DOs}
\begin{itemize}
\item A global fee pool can be described.
\item Determine if auto-bonding fees is compatible with inflation and comission rates in conjunction
\item mention storage optimization in the uniform inflation and iteration over slashing case: only have one storage location w/ refcount of the product of inflation results
\item Remove iteration over slashes by the same normalization trick used in inflation
\item Add equation numbers
\item perhaps re-organize so that the no iteration
\end{itemize}
\end{document}