// disconnectNode disconnects a stake node from itself and returns the state of // the parent node. The database transaction should be included if the // UndoTicketDataSlice or tickets are nil in order to look up the undo data or // tickets from the database. func disconnectNode(node *Node, parentHeader wire.BlockHeader, parentUtds UndoTicketDataSlice, parentTickets []chainhash.Hash, dbTx database.Tx) (*Node, error) { // Edge case for the parent being the genesis block. if node.height == 1 { return genesisNode(node.params), nil } if node == nil { return nil, fmt.Errorf("missing stake node pointer input when " + "disconnecting") } // The undo ticket slice is normally stored in memory for the most // recent blocks and the sidechain, but it may be the case that it // is missing because it's in the mainchain and very old (thus // outside the node cache). In this case, restore this data from // disk. if parentUtds == nil || parentTickets == nil { if dbTx == nil { return nil, stakeRuleError(ErrMissingDatabaseTx, "needed to "+ "look up undo data in the database, but no dbtx passed") } var err error parentUtds, err = ticketdb.DbFetchBlockUndoData(dbTx, node.height-1) if err != nil { return nil, err } parentTickets, err = ticketdb.DbFetchNewTickets(dbTx, node.height-1) if err != nil { return nil, err } } restoredNode := &Node{ height: node.height - 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: parentUtds, databaseBlockTickets: parentTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // Iterate through the block undo data and write all database // changes to the respective treap, reversing all the changes // added when the child block was added to the chain. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, undo := range node.databaseUndoUpdate { var err error k := tickettreap.Key(undo.TicketHash) v := &tickettreap.Value{ Height: undo.TicketHeight, Missed: undo.Missed, Revoked: undo.Revoked, Spent: undo.Spent, Expired: undo.Expired, } switch { // All flags are unset; this is a newly added ticket. // Remove it from the list of live tickets. case !undo.Missed && !undo.Revoked && !undo.Spent: restoredNode.liveTickets, err = safeDelete(restoredNode.liveTickets, k) if err != nil { return nil, err } // The ticket was missed and revoked. It needs to // be moved from the revoked ticket treap to the // missed ticket treap. case undo.Missed && undo.Revoked: v.Revoked = false restoredNode.revokedTickets, err = safeDelete(restoredNode.revokedTickets, k) if err != nil { return nil, err } restoredNode.missedTickets, err = safePut(restoredNode.missedTickets, k, v) if err != nil { return nil, err } // The ticket was missed and was previously live. // Remove it from the missed tickets bucket and // move it to the live tickets bucket. case undo.Missed && !undo.Revoked: // Expired tickets could never have been // winners. if !undo.Expired { restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) } else { v.Expired = false } v.Missed = false restoredNode.missedTickets, err = safeDelete(restoredNode.missedTickets, k) if err != nil { return nil, err } restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } // The ticket was spent. Reinsert it into the live // tickets treap and add it to the list of next // winners. case undo.Spent: v.Spent = false restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } default: return nil, stakeRuleError(ErrMemoryCorruption, "unknown ticket state in undo data") } } if node.height >= uint32(node.params.StakeValidationHeight) { phB, err := parentHeader.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(phB) _, err = findTicketIdxs(int64(restoredNode.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(restoredNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return restoredNode, nil }
// LoadBestNode is used when the blockchain is initialized, to get the initial // stake node from the database bucket. The blockchain must pass the height // and the blockHash to confirm that the ticket database is on the same // location in the blockchain as the blockchain itself. This function also // checks to ensure that the database has not failed the upgrade process and // reports the current version. Upgrades are also handled by this function, // when they become applicable. func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params *chaincfg.Params) (*Node, error) { info, err := ticketdb.DbFetchDatabaseInfo(dbTx) if err != nil { return nil, err } // Compare the tip and make sure it matches. state, err := ticketdb.DbFetchBestState(dbTx) if err != nil { return nil, err } if state.Hash != blockHash || state.Height != height { return nil, stakeRuleError(ErrDatabaseCorrupt, "best state corruption") } // Restore the best node treaps form the database. node := new(Node) node.height = height node.params = params node.liveTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName) if err != nil { return nil, err } if node.liveTickets.Len() != int(state.Live) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("live tickets corruption (got "+ "%v in state but loaded %v)", int(state.Live), node.liveTickets.Len())) } node.missedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.MissedTicketsBucketName) if err != nil { return nil, err } if node.missedTickets.Len() != int(state.Missed) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("missed tickets corruption (got "+ "%v in state but loaded %v)", int(state.Missed), node.missedTickets.Len())) } node.revokedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.RevokedTicketsBucketName) if err != nil { return nil, err } if node.revokedTickets.Len() != int(state.Revoked) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("revoked tickets corruption (got "+ "%v in state but loaded %v)", int(state.Revoked), node.revokedTickets.Len())) } // Restore the node undo and new tickets data. node.databaseUndoUpdate, err = ticketdb.DbFetchBlockUndoData(dbTx, height) if err != nil { return nil, err } node.databaseBlockTickets, err = ticketdb.DbFetchNewTickets(dbTx, height) if err != nil { return nil, err } // Restore the next winners for the node. node.nextWinners = make([]chainhash.Hash, 0) if node.height >= uint32(node.params.StakeValidationHeight-1) { node.nextWinners = make([]chainhash.Hash, len(state.NextWinners)) for i := range state.NextWinners { node.nextWinners[i] = state.NextWinners[i] } // Calculate the final state from the block header. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, ticketHash := range node.nextWinners { stateBuffer = append(stateBuffer, ticketHash[:]...) } hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) _, err = findTicketIdxs(int64(node.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(node.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } log.Infof("Stake database version %v loaded", info.Version) return node, nil }
// connectNode connects a child to a parent stake node, returning the // modified stake node for the child. It is important to keep in mind that // the argument node is the parent node, and that the child stake node is // returned after subsequent modification of the parent node's immutable // data. func connectNode(node *Node, header wire.BlockHeader, ticketsSpentInBlock, revokedTickets, newTickets []chainhash.Hash) (*Node, error) { if node == nil { return nil, fmt.Errorf("missing stake node pointer input when connecting") } connectedNode := &Node{ height: node.height + 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: make(UndoTicketDataSlice, 0), databaseBlockTickets: newTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // We only have to deal with voted related issues and expiry after // StakeEnabledHeight. var err error if connectedNode.height >= uint32(connectedNode.params.StakeEnabledHeight) { // Basic sanity check. for i := range ticketsSpentInBlock { if !hashInSlice(ticketsSpentInBlock[i], node.nextWinners) { return nil, stakeRuleError(ErrUnknownTicketSpent, fmt.Sprintf("unknown ticket %v spent in block", ticketsSpentInBlock[i])) } } // Iterate through all possible winners and construct the undo data, // updating the live and missed ticket treaps as necessary. We need // to copy the value here so we don't modify it in the previous treap. for _, ticket := range node.nextWinners { k := tickettreap.Key(ticket) v, err := safeGet(connectedNode.liveTickets, k) if err != nil { return nil, err } // If it's spent in this block, mark it as being spent. Otherwise, // it was missed. Spent tickets are dropped from the live ticket // bucket, while missed tickets are pushed to the missed ticket // bucket. Because we already know from the above check that the // ticket should still be in the live tickets treap, we probably // do not have to use the safe delete functions, but do so anyway // just to be safe. if hashInSlice(ticket, ticketsSpentInBlock) { v.Spent = true v.Missed = false connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } } else { v.Spent = false v.Missed = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, k, v) if err != nil { return nil, err } } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Find the expiring tickets and drop them as well. We already know what // the winners are from the cached information in the previous block, so // no drop the results of that here. toExpireHeight := uint32(0) if connectedNode.height > uint32(connectedNode.params.TicketExpiry) { toExpireHeight = connectedNode.height - uint32(connectedNode.params.TicketExpiry) } expired := fetchExpired(toExpireHeight, connectedNode.liveTickets) for _, treapKey := range expired { v, err := safeGet(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } v.Missed = true v.Expired = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, *treapKey, v) if err != nil { return nil, err } ticketHash := chainhash.Hash(*treapKey) connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticketHash, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Process all the revocations, moving them from the missed to the // revoked treap and recording them in the undo data. for _, revokedTicket := range revokedTickets { v, err := safeGet(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } v.Revoked = true connectedNode.missedTickets, err = safeDelete(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } connectedNode.revokedTickets, err = safePut(connectedNode.revokedTickets, tickettreap.Key(revokedTicket), v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: revokedTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } } // Add all the new tickets. for _, newTicket := range newTickets { k := tickettreap.Key(newTicket) v := &tickettreap.Value{ Height: connectedNode.height, Missed: false, Revoked: false, Spent: false, Expired: false, } connectedNode.liveTickets, err = safePut(connectedNode.liveTickets, k, v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: newTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // The first block voted on is at StakeEnabledHeight, so begin calculating // winners at the block before StakeEnabledHeight. if connectedNode.height >= uint32(connectedNode.params.StakeValidationHeight-1) { // Find the next set of winners. hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) idxs, err := findTicketIdxs(int64(connectedNode.liveTickets.Len()), int(connectedNode.params.TicketsPerBlock), prng) if err != nil { return nil, err } stateBuffer := make([]byte, 0, (connectedNode.params.TicketsPerBlock+1)*chainhash.HashSize) nextWinnersKeys, err := fetchWinners(idxs, connectedNode.liveTickets) if err != nil { return nil, err } for _, treapKey := range nextWinnersKeys { ticketHash := chainhash.Hash(*treapKey) connectedNode.nextWinners = append(connectedNode.nextWinners, ticketHash) stateBuffer = append(stateBuffer, ticketHash[:]...) } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(connectedNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return connectedNode, nil }
// 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 }