DCP: 0001 Title: New Stake Difficulty Algorithm Author: Dave Collins <[email protected]> Status: Active Created: 2017-05-04 License: CC0-1.0 License-Code: ISC
This specifies a proposed replacement algorithm for determining the stake difficulty (commonly called the ticket price).
The current stake difficulty algorithm satisfies the dead minimum requirement of maintaining a relatively stable ticket pool size which is important since the ticket pool size growing too large or too small can substantially alter the social contract between stakeholders and the network, disrupting the predictable nature of average time to vote a ticket, percentage of tickets that expire, and, consequently, the expected rate of return.
However, the current algorithm also exhibits several shortcomings. Most notably, it allows the prices to swing too wildly which encourages all tickets to be purchased during a single interval when the price is artificially low, followed by discouraging any purchases at all for several intervals due to the price becoming artificially high. In short, very limited price exploration occurs which leads to several undesirable outcomes and behaviors such as a poor user experience due to intense fee competition during the single interval where tickets can reasonably be purchased and huge swings in mining hash power since miners direct large amounts of hash power at the network during the high fee interval and remove it after the interval is over.
This proposal resolves all of these issues by replacing the stake difficulty algorithm completely with a new algorithm that adheres to the following requirements and goals of an ideal stake difficulty algorithm:
- The pool size must remain fairly stable around the target pool size since sizes that are too large or too small would substantially alter the social contract between stakeholders and the network, disrupting the predictable nature of average time to vote a ticket, percentage of tickets that expire, and the rate of return. This is by far the most important requirement.
- The stake difficulty must not fall into a resonant pattern that only encourages ticket purchases during a single interval.
- The stake difficulty must adjust quickly enough to encourage or discourage the desired purchasing behavior necessary to maintain pool size stability and should ideally adjust smoothly when the system is running at or near equilibrium to encourage good price exploration and discourage fee wars.
- The algorithm must be able to absorb sudden surges and ebbs in demand and staking proportions and recover back to expected steady state behavior.
- The algorithm must not be overfitted to a specific demand distribution function (DDF) or the specific mainnet parameters since it will be used on the testnet and simnet networks and the parameters of mainnet could change in a future hard fork vote.
- The algorithm must be reasonably efficient to compute.
- The algorithm should ideally be simple to understand and implement.
In order to facilitate better compatibility across implementations and languages, the formulas defined by the specification make use of integer math instead of floating point math as denoted by use of the floor function. This is highly desirable for consensus code since floating point math can be problematic across languages due to issues such as rounding errors and uncertainty in their respective libraries.
The actual total coin supply as of a given block height depends on many factors such as the number of votes included in every prior block and whether or not any of the prior blocks have been invalidated by stakeholders.
In order to avoid this complexity, the proposed algorithm instead makes use of an estimate of the total coin supply as a function of a given block height as follows:
Explanation of terms:
Cest = Estimated total coin supply for a given height
h = The height for which the coin supply is to be estimated
P = The coins generated by the first block (1,680,000 X 108 on mainnet
)
I = The number of blocks in the subsidy reduction interval (6144 on mainnet
)
c0 = The base coin subsidy before any reductions (3,119,582,664 on mainnet
)
Rm = The coin subsidy reduction multiplier (100 on mainnet
)
Rd = The coin subsidy reduction divisor (101 on mainnet
)
The stake difficulty is calculated at each stake difficulty retarget interval and remains the same throughout the entire interval.
Explanation of terms:
SN = Stake difficulty of the current interval
SN-1 = Stake difficulty of the previous interval
PN = Current pool size including immature tickets
PN-1 = Previous interval's pool size including immature tickets
T = Target pool size to maintain including ticket maturity period (42,240 on mainnet
)
Cest = Estimated total current coin supply
B = Number of blocks with max votes needed to exhaust target pool size (8,192 on mainnet
)
The general idea of the proposed algorithm is rooted in the concept of forces that react to and drive changes to the pool size. The first force acts to counteract the relative change in the pool size, while the second force acts as a restorative force to push the pool size towards the target value. There are minimum and maximum bounds imposed to prevent runaway behavior.
The following is the general form of the proposed stake difficulty algorithm:
Explanation of terms:
SN = Stake difficulty of the current interval
SN-1 = Stake difficulty of the previous interval
Fc = Force to counteract relative change in pool size
Fr = Restorative force to push the pool size towards target
Slb = Lower bound of final computed stake difficulty
Sub = Upper bound of final computed stake difficulty
The following specializes the general form of the algorithm by defining the forces and bounds:
Explanation of terms:
PN = Current pool size including immature tickets
PN-1 = Previous interval's pool size including immature tickets
T = Target pool size to maintain including ticket maturity period (42,240 on mainnet
)
Slb = Lower bound of final computed stake difficulty
Sub = Upper bound of final computed stake difficulty
Cest = Estimated total current coin supply
B = Number of blocks with max votes needed to exhaust target pool size (8,192 on mainnet
)
Finally, the following rearranges and simplifies the formula to use integer math which, as mentioned in the specification, is highly desirable for consensus code since floating point math can be problematic across languages due to issues such as rounding errors and uncertainty in their respective libraries. The is the final form used in the specification:
Explanation of terms:
SN = Stake difficulty of the current interval
SN-1 = Stake difficulty of the previous interval
PN = Current pool size including immature tickets
PN-1 = Previous interval's pool size including immature tickets
T = Target pool size to maintain including ticket maturity period (42,240 on mainnet
)
Cest = Estimated total current coin supply
B = Number of blocks with max votes needed to exhaust target pool size (8,192 on mainnet
)
Before showing the results, it is worth noting that dcrstakesim, the software used to perform these simulations, intentionally aims for several worst case scenarios in order to help ensure the algorithm behaves well under extreme conditions and attack scenarios:
- All coins are managed as a single entity to simulate multiple stakeholders conspiring to manipulate the price. In real world usage scenarios, the coins are distributed among different parties who act in their own best interest which is more stable than the simulated behavior.
- The simulator provides much larger surges and ebbs in demand and staking proportions as compared with likely real world staking. More concretely, it instantly surges the amount of staked coins by a full 20% of the entire supply while simultaneously doubling the demand and then drops them by the same. This surge and ebb is signified by the section highlighted in yellow.
- One of the demand distribution functions (DDF B) solely chases nominal estimated yield which is quite irrational in that it completely ignores things like opportunity cost, the volume-weighted average price, and the fact that the actual expected yield is lower when the pool size is larger. The vast majority of real-world stakers generally make much more rational decisions.
The simulator was used to test 7 different public proposals and several more private ones. This github issue contains the detailed results of simulations run on all public proposals. For brevity, only the results for two of the demand distribution functions against the current algorithm and the proposed algorithm are provided here.
As a point of comparison, the following graphs show the simulation run using two different demand distribution functions with the current algorithm this proposal replaces. It can be seen how the simulation closely mirrors the actual behavior on mainnet with DDF A:
The following graphs show the simulations run using two different demand distribution functions with the proposed algorithm out through block 500,000:
Strengths:
- Both the pool size and ticket price are quite stable
- The algorithm itself is easy to understand and can be efficiently computed
- The algorithm handles the initial bringup period well with realistic and rational staking behavior
- The algorithm behaves nicely under extreme surge conditions
- The algorithm handles steady state properly
- The algorithm recovers well from sudden shocks to the system
- The initial bringup period with the more irrational staking behavior (DDF B) allows the pool size to surge to roughly 70k
This proposal satisfies all of the hard requirements for a stake difficulty algorithm and manages to achieve the ideal goals as well. For example, the mathematical form of the algorithm is simple and its theory of operation is easy to understand. The algorithm also lends itself well to implementation in multiple programming languages.
After running over 300 different simulations with this algorithm and manually tweaking the DDFs to intentionally throw various forms of irrational and attack-like behavior at it, the only notable weakness found is that under unrealistically extreme conditions the pool size can spike up higher than would be ideal, however, that is mitigated by the fact it fairly quickly self corrects and returns to the desired target pool size versus experiencing the runaway behavior of nearly all other algorithms tested, including the existing algorithm on mainnet, under those same circumstances.
This proposal will be deployed to mainnet using the standard Decred on-chain voting infrastructure as follows:
Name | Setting | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Deployment Version | 4 | ||||||||||||
Agenda ID | sdiffalgorithm | ||||||||||||
Agenda Description | Change stake difficulty algorithm as defined in DCP0001 | ||||||||||||
Start Time | 1493164800 (Apr 26th, 2017 00:00:00 +0000 UTC) | ||||||||||||
Expire Time | 1524700800 (Apr 26th, 2018 00:00:00 +0000 UTC) | ||||||||||||
Mask | 0x06 (Bits 1 and 2) | ||||||||||||
Choices |
|
This proposal was approved by the stakeholder voting process and is now active.
Implementations MAY optimize their enforcement activation logic to apply the new
rules specified by this proposal to the Active
block and all of its
descendants as opposed to tallying historical votes.
Status | Block Hash | Block Height |
---|---|---|
Voting Started | 00000000000000796ef26549e567547f3a7fdf67cd22b478fef6f4c4a72838f6 | 133120 |
Locked In | 00000000000000ca1eaaeebcdd21e86c95fc6f0103c8f0b8a868d4b197b46c12 | 141184 |
Active | 000000000000001bf88e81e7b71194e557bb4df70fda0792fd76a0d50977f789 | 149248 |
This is a hard-forking change to the Decred consensus. This means that once the agenda is voted in and becomes locked in, anybody running code that fully validates blocks must upgrade before the activation time or they will risk rejecting a chain containing a stake difficulty that does not match the value calculated by the new algorithm.
Other software, such as statistics dashboards, will need to either make use
of the estimatestakediff
RPC or update their estimation code
according to the specification herein.
// estimateSupply returns an estimate of the coin supply for the provided block
// height. This is primarily used in the stake difficulty algorithm and relies
// on an estimate to simplify the necessary calculations. The actual total
// coin supply as of a given block height depends on many factors such as the
// number of votes included in every prior block (not including all votes
// reduces the subsidy) and whether or not any of the prior blocks have been
// invalidated by stakeholders thereby removing the PoW subsidy for them.
func estimateSupply(height int64) int64 {
// These parameters are ordinarily unique per chain and thus should be
// passed into the function via a chain parameters structure, however,
// they are defined as constants with the mainnet parameters here for the
// purposes of providing a self-contained function for the DCP.
const (
mainnetBlockOneSubsidy = 1680000 * 100000000
mainnetSubsidyReductionInterval = 6144
mainnetBaseSubsidy = 3119582664
mainnetSubsidyReductionMultiplier = 100
mainnetSubsidyReductionDivisor = 101
)
// No subsidy for genesis block or prior.
if height <= 0 {
return 0
}
// Estimate the supply by calculating the full block subsidy for each
// reduction interval and multiplying it the number of blocks in the
// interval then adding the subsidy produced by number of blocks in the
// current interval.
supply := int64(mainnetBlockOneSubsidy)
reductions := int64(height) / mainnetSubsidyReductionInterval
subsidy := int64(mainnetBaseSubsidy)
for i := int64(0); i < reductions; i++ {
supply += mainnetSubsidyReductionInterval * subsidy
subsidy *= mainnetSubsidyReductionMultiplier
subsidy /= mainnetSubsidyReductionDivisor
}
supply += (1 + int64(height)%mainnetSubsidyReductionInterval) * subsidy
// Blocks 0 and 1 have special subsidy amounts that have already been
// added above, so remove what their subsidies would have normally been
// which were also added above.
supply -= mainnetBaseSubsidy * 2
return supply
}
// calcNextStakeDiff calculates the next stake difficulty for the given set
// of parameters using the algorithm defined in this document.
//
// This function contains the heart of the algorithm and thus is separated for
// use in both the actual stake difficulty calculation as well as estimation.
//
// The caller must perform all of the necessary chain traversal in order to
// get the current difficulty, previous retarget interval's pool size plus
// its immature tickets, as well as the current pool size plus immature tickets.
func calcNextStakeDiff(nextHeight, curDiff, prevPoolSizeAll, curPoolSizeAll int64) int64 {
// These parameters are ordinarily unique per chain and thus should be
// passed into the function via a chain parameters structure, however,
// they are defined as constants with the mainnet parameters here for the
// purposes of providing a self-contained function for the DCP.
const (
votesPerBlock = 5
ticketPoolSize = 8192
ticketMaturity = 256
targetPoolSizeAll = votesPerBlock * (ticketPoolSize + ticketMaturity)
minimumStakeDifficulty = 200000000
)
// Calculate the difficulty according to the formula defined in this
// document.
curPoolSizeAllBig := big.NewInt(curPoolSizeAll)
nextDiffBig := big.NewInt(curDiff)
nextDiffBig.Mul(nextDiffBig, curPoolSizeAllBig)
nextDiffBig.Mul(nextDiffBig, curPoolSizeAllBig)
nextDiffBig.Div(nextDiffBig, big.NewInt(prevPoolSizeAll))
nextDiffBig.Div(nextDiffBig, big.NewInt(targetPoolSizeAll))
// Limit the new stake difficulty between the minimum allowed stake
// difficulty and a maximum value that is relative to the total supply.
nextDiff := nextDiffBig.Int64()
estimatedSupply := estimateSupply(nextHeight)
maximumStakeDiff := estimatedSupply / ticketPoolSize
if nextDiff > maximumStakeDiff {
nextDiff = maximumStakeDiff
}
if nextDiff < minimumStakeDifficulty {
nextDiff = minimumStakeDifficulty
}
return nextDiff
}
A reference implementation is provided by this pull request:
The following test vectors are provided in order to facilitate testing across
implementations. These are the expected values for mainnet
.
Height | Estimated Supply (in atoms) |
---|---|
-1 | 0 |
0 | 0 |
1 | 168000000000000 |
2 | 168003119582664 |
3 | 168006239165328 |
6143 | 187160476722288 |
6144 | 187163565417994 |
6145 | 187166654113700 |
12287 | 206137423139952 |
12288 | 206140481254512 |
12289 | 206143539369072 |
116735 | 501461431196784 |
116736 | 501464013399865 |
116737 | 501466595602946 |
442367 | 1158182625545328 |
442368 | 1158184149449220 |
442369 | 1158185673353112 |
2457599 | 2067664381463664 |
2457600 | 2067664439747298 |
2457601 | 2067664498030932 |
11010045 | 2103831507356782 |
11010046 | 2103831507356783 |
11010047 | 2103831507356784 |
11010048 | 2103831507356784 |
11010049 | 2103831507356784 |
Next Height | Current (in atoms) | Previous Pool Size | Current Pool Size | Expected (in atoms) |
---|---|---|---|---|
0 | 200000000 | 0 | 0 | 200000000 |
144 | 200000000 | 0 | 0 | 200000000 |
288 | 200000000 | 0 | 620 | 200000000 |
432 | 200000000 | 620 | 2060 | 200000000 |
432 | 200000000 | 620 | 3500 | 200000000 |
576 | 200000000 | 2060 | 4930 | 200000000 |
720 | 200000000 | 4930 | 6360 | 200000000 |
864 | 200000000 | 6360 | 9250 | 200000000 |
1008 | 200000000 | 9250 | 10680 | 200000000 |
1152 | 200000000 | 10680 | 13570 | 200000000 |
1296 | 200000000 | 13570 | 15000 | 200000000 |
1440 | 200000000 | 15000 | 17890 | 200000000 |
1584 | 200000000 | 17890 | 19320 | 200000000 |
1728 | 200000000 | 19320 | 22210 | 200000000 |
1872 | 200000000 | 22210 | 23640 | 200000000 |
2016 | 200000000 | 23640 | 26530 | 200000000 |
2160 | 200000000 | 26530 | 27960 | 200000000 |
2304 | 200000000 | 27960 | 30850 | 200000000 |
2304 | 200000000 | 38040 | 40920 | 208418769 |
2448 | 200000000 | 30850 | 32280 | 200000000 |
2448 | 208418769 | 40920 | 43800 | 231326567 |
2592 | 200000000 | 32280 | 35170 | 200000000 |
2592 | 231326567 | 43800 | 46680 | 272451490 |
2736 | 200000000 | 35170 | 36600 | 200000000 |
2736 | 272451490 | 46680 | 49560 | 339388424 |
2880 | 200000000 | 36600 | 39490 | 201743368 |
2880 | 339388424 | 49560 | 52440 | 445827839 |
3024 | 201743368 | 39490 | 40776 | 201093236 |
3024 | 445827839 | 52440 | 55320 | 615949254 |
3168 | 201093236 | 40776 | 43667 | 222625877 |
3168 | 615949254 | 55320 | 58200 | 892862990 |
3312 | 222625877 | 43667 | 44808 | 242331291 |
3312 | 892862990 | 58200 | 61080 | 1354989669 |
3456 | 242331291 | 44808 | 47700 | 291317641 |
3456 | 1354989669 | 61080 | 63960 | 2148473276 |
3600 | 2148473276 | 63960 | 66840 | 3552797658 |
3744 | 3552797658 | 66840 | 69720 | 6116808441 |
3888 | 6116808441 | 69720 | 71592 | 10645659768 |
3888 | 6116808441 | 69720 | 72600 | 10947547379 |
4032 | 10645659768 | 71592 | 71599 | 18046712136 |
4032 | 10947547379 | 72600 | 75480 | 20338554623 |
4176 | 200000000 | 37740 | 38785 | 200000000 |
4176 | 18046712136 | 71599 | 71217 | 22097687698 |
4176 | 20338554623 | 75480 | 77965 | 22097687698 |
4320 | 22097687698 | 71217 | 70497 | 22152524112 |
4464 | 22152524112 | 70497 | 69777 | 22207360526 |
The algorithm in this proposal is based upon the collaborative work of many individuals all of whom either proposed potential new algorithms or otherwise provided feedback (alphabetical order):
- @animedow
- Edson Brusque (@edsonbrusque)
- Jonathan Chappelow (@chappjc)
- Dave Collins (@davecgh)
- Charlie Lee (@coblee)
- @raedah
- Julian Y (@jyap808)
- Jake Yocom-Piatt (@behindtext)
Thanks to the following individuals who provided valuable feedback during the review process of this proposal:
- Jonathan Zeppettini
- @kandiru
- John C. Vernaleo (@jcvernaleo)
This document is licensed under the CC0-1.0: Creative Commons CC0 1.0 Universal license.
The code is licensed under the ISC License.