[NOD-1464] difficulty refactoring (#986)

* Refactor the Difficulty adjastment to the new design

* Add the necessary things to the factory for the DAA constructor

* Add missing dagParams to difficultymanager constructor

* Use DAGTraversal for blueBlockWindow, and don't store PowMax compactBits
This commit is contained in:
Elichai Turkel 2020-11-04 11:35:29 +02:00 committed by GitHub
parent 6a46cb2be6
commit ce95c6dc9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 229 additions and 5 deletions

View File

@ -90,7 +90,15 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
pastMedianTimeManager,
ghostdagDataStore)
difficultyManager := difficultymanager.New(
ghostdagManager)
dbManager,
ghostdagManager,
ghostdagDataStore,
blockHeaderStore,
dagTopologyManager,
dagTraversalManager,
dagParams.PowMax,
dagParams.DifficultyAdjustmentWindowSize,
dagParams.TargetTimePerBlock)
coinbaseManager := coinbasemanager.New(
dbManager,
ghostdagDataStore,

View File

@ -0,0 +1,91 @@
package difficultymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/bigintpool"
"github.com/pkg/errors"
"math"
"math/big"
"sort"
)
type difficultyBlock struct {
timeInMilliseconds int64
Bits uint32
}
type blockWindow []difficultyBlock
func (dm *difficultyManager) getDifficultyBlock(blockHash *externalapi.DomainHash) (difficultyBlock, error) {
header, err := dm.headerStore.BlockHeader(dm.databaseContext, blockHash)
if err != nil {
return difficultyBlock{}, err
}
return difficultyBlock{
timeInMilliseconds: header.TimeInMilliseconds,
Bits: header.Bits,
}, nil
}
// blueBlockWindow returns a blockWindow of the given size that contains the
// blues in the past of startindNode, sorted by GHOSTDAG order.
// If the number of blues in the past of startingNode is less then windowSize,
// the window will be padded by genesis blocks to achieve a size of windowSize.
func (dm *difficultyManager) blueBlockWindow(startingNode *externalapi.DomainHash, windowSize uint64) (blockWindow, error) {
window := make(blockWindow, 0, windowSize)
windowHashes, err := dm.dagTraversalManager.BlueWindow(startingNode, windowSize)
if err != nil {
return nil, err
}
for _, hash := range windowHashes {
block, err := dm.getDifficultyBlock(hash)
if err != nil {
return nil, err
}
window = append(window, block)
}
return window, nil
}
func (window blockWindow) minMaxTimestamps() (min, max int64) {
min = math.MaxInt64
max = 0
for _, block := range window {
if block.timeInMilliseconds < min {
min = block.timeInMilliseconds
}
if block.timeInMilliseconds > max {
max = block.timeInMilliseconds
}
}
return
}
func (window blockWindow) averageTarget(averageTarget *big.Int) {
averageTarget.SetInt64(0)
target := bigintpool.Acquire(0)
defer bigintpool.Release(target)
for _, block := range window {
util.CompactToBigWithDestination(block.Bits, target)
averageTarget.Add(averageTarget, target)
}
windowLen := bigintpool.Acquire(int64(len(window)))
defer bigintpool.Release(windowLen)
averageTarget.Div(averageTarget, windowLen)
}
func (window blockWindow) medianTimestamp() (int64, error) {
if len(window) == 0 {
return 0, errors.New("Cannot calculate median timestamp for an empty block window")
}
timestamps := make([]int64, len(window))
for i, node := range window {
timestamps[i] = node.timeInMilliseconds
}
sort.Sort(timeSorter(timestamps))
return timestamps[len(timestamps)/2], nil
}

View File

@ -3,22 +3,120 @@ package difficultymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/bigintpool"
"math/big"
"time"
)
// DifficultyManager provides a method to resolve the
// difficulty value of a block
type difficultyManager struct {
ghostdagManager model.GHOSTDAGManager
databaseContext model.DBReader
ghostdagManager model.GHOSTDAGManager
ghostdagStore model.GHOSTDAGDataStore
headerStore model.BlockHeaderStore
dagTopologyManager model.DAGTopologyManager
dagTraversalManager model.DAGTraversalManager
powMax *big.Int
difficultyAdjustmentWindowSize uint64
targetTimePerBlock time.Duration
}
// New instantiates a new DifficultyManager
func New(ghostdagManager model.GHOSTDAGManager) model.DifficultyManager {
func New(
databaseContext model.DBReader,
ghostdagManager model.GHOSTDAGManager,
ghostdagStore model.GHOSTDAGDataStore,
headerStore model.BlockHeaderStore,
dagTopologyManager model.DAGTopologyManager,
dagTraversalManager model.DAGTraversalManager,
powMax *big.Int,
difficultyAdjustmentWindowSize uint64,
targetTimePerBlock time.Duration,
) model.DifficultyManager {
return &difficultyManager{
ghostdagManager: ghostdagManager,
databaseContext: databaseContext,
ghostdagManager: ghostdagManager,
ghostdagStore: ghostdagStore,
headerStore: headerStore,
dagTopologyManager: dagTopologyManager,
dagTraversalManager: dagTraversalManager,
powMax: powMax,
difficultyAdjustmentWindowSize: difficultyAdjustmentWindowSize,
targetTimePerBlock: targetTimePerBlock,
}
}
// RequiredDifficulty returns the difficulty required for some block
func (dm *difficultyManager) RequiredDifficulty(blockHash *externalapi.DomainHash) (uint32, error) {
return 0, nil
parents, err := dm.dagTopologyManager.Parents(blockHash)
if err != nil {
return 0, err
}
// Genesis block
if len(parents) == 0 {
return util.BigToCompact(dm.powMax), nil
}
// find bluestParent
bluestParent := parents[0]
bluestGhostDAG, err := dm.ghostdagStore.Get(dm.databaseContext, bluestParent)
if err != nil {
return 0, err
}
for i := 1; i < len(parents); i++ {
parentGhostDAG, err := dm.ghostdagStore.Get(dm.databaseContext, parents[i])
if err != nil {
return 0, err
}
newBluest, err := dm.ghostdagManager.ChooseSelectedParent(bluestParent, parents[i])
if err != nil {
return 0, err
}
if bluestParent != newBluest {
bluestParent = newBluest
bluestGhostDAG = parentGhostDAG
}
}
// Not enough blocks for building a difficulty window.
if bluestGhostDAG.BlueScore < dm.difficultyAdjustmentWindowSize+1 {
return util.BigToCompact(dm.powMax), nil
}
// Fetch window of dag.difficultyAdjustmentWindowSize + 1 so we can have dag.difficultyAdjustmentWindowSize block intervals
timestampsWindow, err := dm.blueBlockWindow(bluestParent, dm.difficultyAdjustmentWindowSize+1)
if err != nil {
return 0, err
}
windowMinTimestamp, windowMaxTimeStamp := timestampsWindow.minMaxTimestamps()
// Remove the last block from the window so to calculate the average target of dag.difficultyAdjustmentWindowSize blocks
targetsWindow := timestampsWindow[:dm.difficultyAdjustmentWindowSize]
// Calculate new target difficulty as:
// averageWindowTarget * (windowMinTimestamp / (targetTimePerBlock * windowSize))
// The result uses integer division which means it will be slightly
// rounded down.
newTarget := bigintpool.Acquire(0)
defer bigintpool.Release(newTarget)
windowTimeStampDifference := bigintpool.Acquire(windowMaxTimeStamp - windowMinTimestamp)
defer bigintpool.Release(windowTimeStampDifference)
targetTimePerBlock := bigintpool.Acquire(dm.targetTimePerBlock.Milliseconds())
defer bigintpool.Release(targetTimePerBlock)
difficultyAdjustmentWindowSize := bigintpool.Acquire(int64(dm.difficultyAdjustmentWindowSize))
defer bigintpool.Release(difficultyAdjustmentWindowSize)
targetsWindow.averageTarget(newTarget)
newTarget.
Mul(newTarget, windowTimeStampDifference).
Div(newTarget, targetTimePerBlock).
Div(newTarget, difficultyAdjustmentWindowSize)
if newTarget.Cmp(dm.powMax) > 0 {
return util.BigToCompact(dm.powMax), nil
}
newTargetBits := util.BigToCompact(newTarget)
return newTargetBits, nil
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package difficultymanager
// timeSorter implements sort.Interface to allow a slice of timestamps to
// be sorted.
type timeSorter []int64
// Len returns the number of timestamps in the slice. It is part of the
// sort.Interface implementation.
func (s timeSorter) Len() int {
return len(s)
}
// Swap swaps the timestamps at the passed indices. It is part of the
// sort.Interface implementation.
func (s timeSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less returns whether the timstamp with index i should sort before the
// timestamp with index j. It is part of the sort.Interface implementation.
func (s timeSorter) Less(i, j int) bool {
return s[i] < s[j]
}