mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
197 lines
6.7 KiB
Go
197 lines
6.7 KiB
Go
package reachabilitymanager
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/pkg/errors"
|
|
"math"
|
|
)
|
|
|
|
func newReachabilityInterval(start uint64, end uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{Start: start, End: end}
|
|
}
|
|
|
|
// intervalSize returns the size of this interval. Note that intervals are
|
|
// inclusive from both sides.
|
|
func intervalSize(ri *model.ReachabilityInterval) uint64 {
|
|
return ri.End - ri.Start + 1
|
|
}
|
|
|
|
// intervalIncrease returns a ReachabilityInterval with offset added to start and end
|
|
func intervalIncrease(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start + offset,
|
|
End: ri.End + offset,
|
|
}
|
|
}
|
|
|
|
// intervalDecrease returns a ReachabilityInterval with offset subtracted from start and end
|
|
func intervalDecrease(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start - offset,
|
|
End: ri.End - offset,
|
|
}
|
|
}
|
|
|
|
// intervalIncreaseStart returns a ReachabilityInterval with offset added to start
|
|
func intervalIncreaseStart(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start + offset,
|
|
End: ri.End,
|
|
}
|
|
}
|
|
|
|
// intervalDecreaseStart returns a ReachabilityInterval with offset reduced from start
|
|
func intervalDecreaseStart(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start - offset,
|
|
End: ri.End,
|
|
}
|
|
}
|
|
|
|
// intervalIncreaseEnd returns a ReachabilityInterval with offset added to end
|
|
func intervalIncreaseEnd(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start,
|
|
End: ri.End + offset,
|
|
}
|
|
}
|
|
|
|
// intervalDecreaseEnd returns a ReachabilityInterval with offset subtracted from end
|
|
func intervalDecreaseEnd(ri *model.ReachabilityInterval, offset uint64) *model.ReachabilityInterval {
|
|
return &model.ReachabilityInterval{
|
|
Start: ri.Start,
|
|
End: ri.End - offset,
|
|
}
|
|
}
|
|
|
|
// intervalSplitInHalf splits this interval by a fraction of 0.5.
|
|
// See splitFraction for further details.
|
|
func intervalSplitInHalf(ri *model.ReachabilityInterval) (
|
|
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
|
|
|
|
return intervalSplitFraction(ri, 0.5)
|
|
}
|
|
|
|
// intervalSplitFraction splits this interval to two parts such that their
|
|
// union is equal to the original interval and the first (left) part
|
|
// contains the given fraction of the original interval's size.
|
|
// Note: if the split results in fractional parts, this method rounds
|
|
// the first part up and the last part down.
|
|
func intervalSplitFraction(ri *model.ReachabilityInterval, fraction float64) (
|
|
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
|
|
|
|
if fraction < 0 || fraction > 1 {
|
|
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
|
|
}
|
|
if intervalSize(ri) == 0 {
|
|
return nil, nil, errors.Errorf("cannot split an empty interval")
|
|
}
|
|
|
|
allocationSize := uint64(math.Ceil(float64(intervalSize(ri)) * fraction))
|
|
left = newReachabilityInterval(ri.Start, ri.Start+allocationSize-1)
|
|
right = newReachabilityInterval(ri.Start+allocationSize, ri.End)
|
|
return left, right, nil
|
|
}
|
|
|
|
// intervalSplitExact splits this interval to exactly |sizes| parts where
|
|
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
|
|
// equal to the interval's size.
|
|
func intervalSplitExact(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
|
|
sizesSum := uint64(0)
|
|
for _, size := range sizes {
|
|
sizesSum += size
|
|
}
|
|
if sizesSum != intervalSize(ri) {
|
|
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
|
|
}
|
|
|
|
intervals := make([]*model.ReachabilityInterval, len(sizes))
|
|
start := ri.Start
|
|
for i, size := range sizes {
|
|
intervals[i] = newReachabilityInterval(start, start+size-1)
|
|
start += size
|
|
}
|
|
return intervals, nil
|
|
}
|
|
|
|
// intervalSplitWithExponentialBias splits this interval to |sizes| parts
|
|
// by the allocation rule described below. This method expects sum(sizes)
|
|
// to be smaller or equal to the interval's size. Every part_i is
|
|
// allocated at least sizes[i] capacity. The remaining budget is
|
|
// split by an exponentially biased rule described below.
|
|
//
|
|
// This rule follows the GHOSTDAG protocol behavior where the child
|
|
// with the largest subtree is expected to dominate the competition
|
|
// for new blocks and thus grow the most. However, we may need to
|
|
// add slack for non-largest subtrees in order to make CPU reindexing
|
|
// attacks unworthy.
|
|
func intervalSplitWithExponentialBias(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
|
|
intervalSize := intervalSize(ri)
|
|
sizesSum := uint64(0)
|
|
for _, size := range sizes {
|
|
sizesSum += size
|
|
}
|
|
if sizesSum > intervalSize {
|
|
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
|
|
}
|
|
if sizesSum == intervalSize {
|
|
return intervalSplitExact(ri, sizes)
|
|
}
|
|
|
|
// Add a fractional bias to every size in the given sizes
|
|
totalBias := intervalSize - sizesSum
|
|
remainingBias := totalBias
|
|
biasedSizes := make([]uint64, len(sizes))
|
|
fractions := exponentialFractions(sizes)
|
|
for i, fraction := range fractions {
|
|
var bias uint64
|
|
if i == len(fractions)-1 {
|
|
bias = remainingBias
|
|
} else {
|
|
bias = uint64(math.Round(float64(totalBias) * fraction))
|
|
if bias > remainingBias {
|
|
bias = remainingBias
|
|
}
|
|
}
|
|
biasedSizes[i] = sizes[i] + bias
|
|
remainingBias -= bias
|
|
}
|
|
return intervalSplitExact(ri, biasedSizes)
|
|
}
|
|
|
|
// exponentialFractions returns a fraction of each size in sizes
|
|
// as follows:
|
|
//
|
|
// fraction[i] = 2^size[i] / sum_j(2^size[j])
|
|
//
|
|
// In the code below the above equation is divided by 2^max(size)
|
|
// to avoid exploding numbers. Note that in 1 / 2^(max(size)-size[i])
|
|
// we divide 1 by potentially a very large number, which will
|
|
// result in loss of float precision. This is not a problem - all
|
|
// numbers close to 0 bear effectively the same weight.
|
|
func exponentialFractions(sizes []uint64) []float64 {
|
|
maxSize := uint64(0)
|
|
for _, size := range sizes {
|
|
if size > maxSize {
|
|
maxSize = size
|
|
}
|
|
}
|
|
fractions := make([]float64, len(sizes))
|
|
for i, size := range sizes {
|
|
fractions[i] = 1 / math.Pow(2, float64(maxSize-size))
|
|
}
|
|
fractionsSum := float64(0)
|
|
for _, fraction := range fractions {
|
|
fractionsSum += fraction
|
|
}
|
|
for i, fraction := range fractions {
|
|
fractions[i] = fraction / fractionsSum
|
|
}
|
|
return fractions
|
|
}
|
|
|
|
// intervalContains returns true if ri contains other.
|
|
func intervalContains(ri *model.ReachabilityInterval, other *model.ReachabilityInterval) bool {
|
|
return ri.Start <= other.Start && other.End <= ri.End
|
|
}
|