From ce95c6dc9d81c5d6c4d69330b7003a6ef083902c Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Wed, 4 Nov 2020 11:35:29 +0200 Subject: [PATCH] [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 --- domain/consensus/factory.go | 10 +- .../difficultymanager/blockwindow.go | 91 +++++++++++++++ .../difficultymanager/difficultymanager.go | 106 +++++++++++++++++- .../processes/difficultymanager/timesorter.go | 27 +++++ 4 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 domain/consensus/processes/difficultymanager/blockwindow.go create mode 100644 domain/consensus/processes/difficultymanager/timesorter.go diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index c6e83c00d..b795830cd 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -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, diff --git a/domain/consensus/processes/difficultymanager/blockwindow.go b/domain/consensus/processes/difficultymanager/blockwindow.go new file mode 100644 index 000000000..520f8268a --- /dev/null +++ b/domain/consensus/processes/difficultymanager/blockwindow.go @@ -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 +} diff --git a/domain/consensus/processes/difficultymanager/difficultymanager.go b/domain/consensus/processes/difficultymanager/difficultymanager.go index 85e2de9f4..adf5edc2b 100644 --- a/domain/consensus/processes/difficultymanager/difficultymanager.go +++ b/domain/consensus/processes/difficultymanager/difficultymanager.go @@ -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 } diff --git a/domain/consensus/processes/difficultymanager/timesorter.go b/domain/consensus/processes/difficultymanager/timesorter.go new file mode 100644 index 000000000..6c54c1051 --- /dev/null +++ b/domain/consensus/processes/difficultymanager/timesorter.go @@ -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] +}