// 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 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 }