Fix for rare consensus bug regarding daa window order (#1934)

* Fix for rare consensus bug: daa window min-time-block was not deterministic when timestamps are equal

* Something is missing

* Extract compare logic to a function with better performance

* typo
This commit is contained in:
Michael Sutton 2022-01-27 20:29:44 +02:00 committed by GitHub
parent 598392d0cf
commit dab1a881fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 12 deletions

View File

@ -1,18 +1,18 @@
package difficultymanager
import (
"math"
"math/big"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/difficulty"
"math"
"math/big"
)
type difficultyBlock struct {
timeInMilliseconds int64
Bits uint32
hash *externalapi.DomainHash
blueWork *big.Int
}
type blockWindow []difficultyBlock
@ -27,6 +27,8 @@ func (dm *difficultyManager) getDifficultyBlock(
return difficultyBlock{
timeInMilliseconds: header.TimeInMilliseconds(),
Bits: header.Bits(),
hash: blockHash,
blueWork: header.BlueWork(),
}, nil
}
@ -53,19 +55,32 @@ func (dm *difficultyManager) blockWindow(stagingArea *model.StagingArea, startin
return window, windowHashes, nil
}
func (window blockWindow) minMaxTimestamps() (min, max int64, minIndex, maxIndex int) {
func ghostdagLess(blockA *difficultyBlock, blockB *difficultyBlock) bool {
switch blockA.blueWork.Cmp(blockB.blueWork) {
case -1:
return true
case 1:
return false
case 0:
return blockA.hash.Less(blockB.hash)
default:
panic("big.Int.Cmp is defined to always return -1/1/0 and nothing else")
}
}
func (window blockWindow) minMaxTimestamps() (min, max int64, minIndex int) {
min = math.MaxInt64
minIndex = math.MaxInt64
minIndex = 0
max = 0
maxIndex = 0
for i, block := range window {
if block.timeInMilliseconds < min {
// If timestamps are equal we ghostdag compare in order to reach consensus on `minIndex`
if block.timeInMilliseconds < min ||
(block.timeInMilliseconds == min && ghostdagLess(&block, &window[minIndex])) {
min = block.timeInMilliseconds
minIndex = i
}
if block.timeInMilliseconds > max {
max = block.timeInMilliseconds
maxIndex = i
}
}
return

View File

@ -115,9 +115,10 @@ func (dm *difficultyManager) requiredDifficultyFromTargetsWindow(targetsWindow b
if len(targetsWindow) < 2 || len(targetsWindow) < dm.difficultyAdjustmentWindowSize {
return dm.genesisBits, nil
}
windowMinTimestamp, windowMaxTimeStamp, windowsMinIndex, _ := targetsWindow.minMaxTimestamps()
windowMinTimestamp, windowMaxTimeStamp, windowMinIndex := targetsWindow.minMaxTimestamps()
// Remove the last block from the window so to calculate the average target of dag.difficultyAdjustmentWindowSize blocks
targetsWindow.remove(windowsMinIndex)
targetsWindow.remove(windowMinIndex)
// Calculate new target difficulty as:
// averageWindowTarget * (windowMinTimestamp / (targetTimePerBlock * windowSize))

View File

@ -35,7 +35,7 @@ func (dm *difficultyManager) estimateNetworkHashesPerSecond(stagingArea *model.S
return 0, nil
}
minWindowTimestamp, maxWindowTimestamp, _, _ := blockWindow.minMaxTimestamps()
minWindowTimestamp, maxWindowTimestamp, _ := blockWindow.minMaxTimestamps()
if minWindowTimestamp == maxWindowTimestamp {
return 0, errors.Errorf("min window timestamp is equal to the max window timestamp")
}