Skip to content

Commit

Permalink
Add estimatestakediff RPC command (#143)
Browse files Browse the repository at this point in the history
The new RPC command estimatestakediff was added. It estimates the
minimum, maximum, and expected next stake difficulty. The caller can
also estimate a new difficulty given a passed number of tickets to be
mined in the remaining difficulty period.
  • Loading branch information
cjepson authored and alexlyp committed Apr 29, 2016
1 parent c9a0afa commit 7fd213b
Show file tree
Hide file tree
Showing 6 changed files with 480 additions and 1 deletion.
325 changes: 325 additions & 0 deletions blockchain/difficulty.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
package blockchain

import (
"fmt"
"math/big"
"time"

"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/wire"
)

var (
Expand Down Expand Up @@ -740,3 +742,326 @@ func (b *BlockChain) calcNextRequiredStakeDifficulty(curNode *blockNode) (int64,
func (b *BlockChain) CalcNextRequiredStakeDifficulty() (int64, error) {
return b.calcNextRequiredStakeDifficulty(b.bestChain)
}

// estimateNextStakeDifficulty returns a user-specified estimate for the next
// stake difficulty, with the passed ticketsInWindow indicating the number of
// fresh stake to pretend exists within this window. Optionally the user can
// also override this variable with useMaxTickets, which simply plugs in the
// maximum number of tickets the user can try.
func (b *BlockChain) estimateNextStakeDifficulty(curNode *blockNode,
ticketsInWindow int64, useMaxTickets bool) (int64, error) {
alpha := b.chainParams.StakeDiffAlpha
stakeDiffStartHeight := int64(b.chainParams.CoinbaseMaturity) +
1
maxRetarget := int64(b.chainParams.RetargetAdjustmentFactor)
TicketPoolWeight := int64(b.chainParams.TicketPoolSizeWeight)

// Number of nodes to traverse while calculating difficulty.
nodesToTraverse := (b.chainParams.StakeDiffWindowSize *
b.chainParams.StakeDiffWindows)

// Genesis block. Block at height 1 has these parameters.
if curNode == nil ||
curNode.height < stakeDiffStartHeight {
return b.chainParams.MinimumStakeDiff, nil
}

// Create a fake blockchain on top of the current best node with
// the number of freshly purchased tickets as indicated by the
// user.
oldDiff := curNode.header.SBits
topNode := curNode
if (curNode.height+1)%b.chainParams.StakeDiffWindowSize != 0 {
nextAdjHeight := ((curNode.height /
b.chainParams.StakeDiffWindowSize) + 1) *
b.chainParams.StakeDiffWindowSize
maxTickets := (nextAdjHeight - curNode.height) *
int64(b.chainParams.MaxFreshStakePerBlock)

// If the user has indicated that the automatically
// calculated maximum amount of tickets should be
// used, plug that in here.
if useMaxTickets {
ticketsInWindow = maxTickets
}

// Double check to make sure there isn't too much.
if ticketsInWindow > maxTickets {
return 0, fmt.Errorf("too much fresh stake to be used "+
"in evaluation requested; max %v, got %v", maxTickets,
ticketsInWindow)
}

// Insert all the tickets into bogus nodes that will be
// used to calculate the next difficulty below.
ticketsToInsert := ticketsInWindow
for i := curNode.height + 1; i < nextAdjHeight; i++ {
emptyHeader := new(wire.BlockHeader)
emptyHeader.Height = uint32(i)

// User a constant pool size for estimate, since
// this has much less fluctuation than freshStake.
// TODO Use a better pool size estimate?
emptyHeader.PoolSize = curNode.header.PoolSize

// Insert the fake fresh stake into each block,
// decrementing the amount we need to use each
// time until we hit 0.
freshStake := b.chainParams.MaxFreshStakePerBlock
if int64(freshStake) > ticketsToInsert {
freshStake = uint8(ticketsToInsert)
ticketsToInsert -= ticketsToInsert
} else {
ticketsToInsert -= int64(b.chainParams.MaxFreshStakePerBlock)
}
emptyHeader.FreshStake = freshStake

// Connect the header.
emptyHeader.PrevBlock = *topNode.hash

// Make up a node hash.
hB, err := emptyHeader.Bytes()
if err != nil {
return 0, err
}
emptyHeaderHash := chainhash.HashFuncH(hB)

thisNode := new(blockNode)
thisNode.header = *emptyHeader
thisNode.hash = &emptyHeaderHash
thisNode.height = i
thisNode.parent = topNode
thisNode.parentHash = topNode.hash
topNode = thisNode
}
}

// The target size of the ticketPool in live tickets. Recast these as int64
// to avoid possible overflows for large sizes of either variable in
// params.
targetForTicketPool := int64(b.chainParams.TicketsPerBlock) *
int64(b.chainParams.TicketPoolSize)

// Initialize bigInt slice for the percentage changes for each window period
// above or below the target.
windowChanges := make([]*big.Int, b.chainParams.StakeDiffWindows)

// Regress through all of the previous blocks and store the percent changes
// per window period; use bigInts to emulate 64.32 bit fixed point.
oldNode := topNode
windowPeriod := int64(0)
weights := uint64(0)

for i := int64(0); ; i++ {
// Store and reset after reaching the end of every window period.
if (i+1)%b.chainParams.StakeDiffWindowSize == 0 {
// First adjust based on ticketPoolSize. Skew the difference
// in ticketPoolSize by max adjustment factor to help
// weight ticket pool size versus tickets per block.
poolSizeSkew := (int64(oldNode.header.PoolSize)-
targetForTicketPool)*TicketPoolWeight + targetForTicketPool

// Don't let this be negative or zero.
if poolSizeSkew <= 0 {
poolSizeSkew = 1
}

curPoolSizeTemp := big.NewInt(poolSizeSkew)
curPoolSizeTemp.Lsh(curPoolSizeTemp, 32) // Add padding
targetTemp := big.NewInt(targetForTicketPool)

windowAdjusted := curPoolSizeTemp.Div(curPoolSizeTemp, targetTemp)

// Weight it exponentially. Be aware that this could at some point
// overflow if alpha or the number of blocks used is really large.
windowAdjusted = windowAdjusted.Lsh(windowAdjusted,
uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha))

// Sum up all the different weights incrementally.
weights += 1 << uint64((b.chainParams.StakeDiffWindows-windowPeriod)*
alpha)

// Store it in the slice.
windowChanges[windowPeriod] = windowAdjusted

// windowFreshStake = 0
windowPeriod++
}

if (i + 1) == nodesToTraverse {
break // Exit for loop when we hit the end.
}

// Get the previous block node.
var err error
tempNode := oldNode
oldNode, err = b.getPrevNodeFromNode(oldNode)
if err != nil {
return 0, err
}

// If we're at the genesis block, reset the oldNode
// so that it stays at the genesis block.
if oldNode == nil {
oldNode = tempNode
}
}

// Sum up the weighted window periods.
weightedSum := big.NewInt(0)
for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ {
weightedSum.Add(weightedSum, windowChanges[i])
}

// Divide by the sum of all weights.
weightsBig := big.NewInt(int64(weights))
weightedSumDiv := weightedSum.Div(weightedSum, weightsBig)

// Multiply by the old stake diff.
oldDiffBig := big.NewInt(oldDiff)
nextDiffBig := weightedSumDiv.Mul(weightedSumDiv, oldDiffBig)

// Right shift to restore the original padding (restore non-fixed point).
nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32)
nextDiffTicketPool := nextDiffBig.Int64()

// Check to see if we're over the limits for the maximum allowable retarget;
// if we are, return the maximum or minimum except in the case that oldDiff
// is zero.
if oldDiff == 0 { // This should never really happen, but in case it does...
return nextDiffTicketPool, nil
} else if nextDiffTicketPool == 0 {
nextDiffTicketPool = oldDiff / maxRetarget
} else if (nextDiffTicketPool / oldDiff) > (maxRetarget - 1) {
nextDiffTicketPool = oldDiff * maxRetarget
} else if (oldDiff / nextDiffTicketPool) > (maxRetarget - 1) {
nextDiffTicketPool = oldDiff / maxRetarget
}

// The target number of new SStx per block for any given window period.
targetForWindow := b.chainParams.StakeDiffWindowSize *
int64(b.chainParams.TicketsPerBlock)

// Regress through all of the previous blocks and store the percent changes
// per window period; use bigInts to emulate 64.32 bit fixed point.
oldNode = topNode
windowFreshStake := int64(0)
windowPeriod = int64(0)
weights = uint64(0)

for i := int64(0); ; i++ {
// Add the fresh stake into the store for this window period.
windowFreshStake += int64(oldNode.header.FreshStake)

// Store and reset after reaching the end of every window period.
if (i+1)%b.chainParams.StakeDiffWindowSize == 0 {
// Don't let fresh stake be zero.
if windowFreshStake <= 0 {
windowFreshStake = 1
}

freshTemp := big.NewInt(windowFreshStake)
freshTemp.Lsh(freshTemp, 32) // Add padding
targetTemp := big.NewInt(targetForWindow)

// Get the percentage change.
windowAdjusted := freshTemp.Div(freshTemp, targetTemp)

// Weight it exponentially. Be aware that this could at some point
// overflow if alpha or the number of blocks used is really large.
windowAdjusted = windowAdjusted.Lsh(windowAdjusted,
uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha))

// Sum up all the different weights incrementally.
weights += 1 <<
uint64((b.chainParams.StakeDiffWindows-windowPeriod)*alpha)

// Store it in the slice.
windowChanges[windowPeriod] = windowAdjusted

windowFreshStake = 0
windowPeriod++
}

if (i + 1) == nodesToTraverse {
break // Exit for loop when we hit the end.
}

// Get the previous block node.
var err error
tempNode := oldNode
oldNode, err = b.getPrevNodeFromNode(oldNode)
if err != nil {
return 0, err
}

// If we're at the genesis block, reset the oldNode
// so that it stays at the genesis block.
if oldNode == nil {
oldNode = tempNode
}
}

// Sum up the weighted window periods.
weightedSum = big.NewInt(0)
for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ {
weightedSum.Add(weightedSum, windowChanges[i])
}

// Divide by the sum of all weights.
weightsBig = big.NewInt(int64(weights))
weightedSumDiv = weightedSum.Div(weightedSum, weightsBig)

// Multiply by the old stake diff.
oldDiffBig = big.NewInt(oldDiff)
nextDiffBig = weightedSumDiv.Mul(weightedSumDiv, oldDiffBig)

// Right shift to restore the original padding (restore non-fixed point).
nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32)
nextDiffFreshStake := nextDiffBig.Int64()

// Check to see if we're over the limits for the maximum allowable retarget;
// if we are, return the maximum or minimum except in the case that oldDiff
// is zero.
if oldDiff == 0 { // This should never really happen, but in case it does...
return nextDiffFreshStake, nil
} else if nextDiffFreshStake == 0 {
nextDiffFreshStake = oldDiff / maxRetarget
} else if (nextDiffFreshStake / oldDiff) > (maxRetarget - 1) {
nextDiffFreshStake = oldDiff * maxRetarget
} else if (oldDiff / nextDiffFreshStake) > (maxRetarget - 1) {
nextDiffFreshStake = oldDiff / maxRetarget
}

// Average the two differences using scaled multiplication.
nextDiff := mergeDifficulty(oldDiff, nextDiffTicketPool, nextDiffFreshStake)

// Check to see if we're over the limits for the maximum allowable retarget;
// if we are, return the maximum or minimum except in the case that oldDiff
// is zero.
if oldDiff == 0 { // This should never really happen, but in case it does...
return oldDiff, nil
} else if nextDiff == 0 {
nextDiff = oldDiff / maxRetarget
} else if (nextDiff / oldDiff) > (maxRetarget - 1) {
nextDiff = oldDiff * maxRetarget
} else if (oldDiff / nextDiff) > (maxRetarget - 1) {
nextDiff = oldDiff / maxRetarget
}

// If the next diff is below the network minimum, set the required stake
// difficulty to the minimum.
if nextDiff < b.chainParams.MinimumStakeDiff {
return b.chainParams.MinimumStakeDiff, nil
}

return nextDiff, nil
}

// EstimateNextStakeDifficulty is the exported version of the above function.
// This function is NOT safe for concurrent access.
func (b *BlockChain) EstimateNextStakeDifficulty(ticketsInWindow int64,
useMaxTickets bool) (int64, error) {
return b.estimateNextStakeDifficulty(b.bestChain, ticketsInWindow,
useMaxTickets)
}
Loading

0 comments on commit 7fd213b

Please sign in to comment.