mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-07 14:46:44 +00:00
[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:
parent
6a46cb2be6
commit
ce95c6dc9d
@ -90,7 +90,15 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
|
|||||||
pastMedianTimeManager,
|
pastMedianTimeManager,
|
||||||
ghostdagDataStore)
|
ghostdagDataStore)
|
||||||
difficultyManager := difficultymanager.New(
|
difficultyManager := difficultymanager.New(
|
||||||
ghostdagManager)
|
dbManager,
|
||||||
|
ghostdagManager,
|
||||||
|
ghostdagDataStore,
|
||||||
|
blockHeaderStore,
|
||||||
|
dagTopologyManager,
|
||||||
|
dagTraversalManager,
|
||||||
|
dagParams.PowMax,
|
||||||
|
dagParams.DifficultyAdjustmentWindowSize,
|
||||||
|
dagParams.TargetTimePerBlock)
|
||||||
coinbaseManager := coinbasemanager.New(
|
coinbaseManager := coinbasemanager.New(
|
||||||
dbManager,
|
dbManager,
|
||||||
ghostdagDataStore,
|
ghostdagDataStore,
|
||||||
|
91
domain/consensus/processes/difficultymanager/blockwindow.go
Normal file
91
domain/consensus/processes/difficultymanager/blockwindow.go
Normal 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
|
||||||
|
}
|
@ -3,22 +3,120 @@ package difficultymanager
|
|||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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
|
// DifficultyManager provides a method to resolve the
|
||||||
// difficulty value of a block
|
// difficulty value of a block
|
||||||
type difficultyManager struct {
|
type difficultyManager struct {
|
||||||
|
databaseContext model.DBReader
|
||||||
ghostdagManager model.GHOSTDAGManager
|
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
|
// 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{
|
return &difficultyManager{
|
||||||
|
databaseContext: databaseContext,
|
||||||
ghostdagManager: ghostdagManager,
|
ghostdagManager: ghostdagManager,
|
||||||
|
ghostdagStore: ghostdagStore,
|
||||||
|
headerStore: headerStore,
|
||||||
|
dagTopologyManager: dagTopologyManager,
|
||||||
|
dagTraversalManager: dagTraversalManager,
|
||||||
|
powMax: powMax,
|
||||||
|
difficultyAdjustmentWindowSize: difficultyAdjustmentWindowSize,
|
||||||
|
targetTimePerBlock: targetTimePerBlock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequiredDifficulty returns the difficulty required for some block
|
// RequiredDifficulty returns the difficulty required for some block
|
||||||
func (dm *difficultyManager) RequiredDifficulty(blockHash *externalapi.DomainHash) (uint32, error) {
|
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
|
||||||
}
|
}
|
||||||
|
27
domain/consensus/processes/difficultymanager/timesorter.go
Normal file
27
domain/consensus/processes/difficultymanager/timesorter.go
Normal 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]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user