Skip to content

Commit

Permalink
Added Dom difficulty controller using the Prime difficullty values
Browse files Browse the repository at this point in the history
  • Loading branch information
kiltsonfire authored and gameofpointers committed Aug 22, 2023
1 parent 5ed0c3f commit 4cc39ac
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 65 deletions.
23 changes: 16 additions & 7 deletions common/big.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import "math/big"

// Common big integers often used
var (
Big1 = big.NewInt(1)
Big2 = big.NewInt(2)
Big3 = big.NewInt(3)
Big0 = big.NewInt(0)
Big32 = big.NewInt(32)
Big256 = big.NewInt(256)
Big257 = big.NewInt(257)
Big0 = big.NewInt(0)
Big1 = big.NewInt(1)
Big2 = big.NewInt(2)
Big3 = big.NewInt(3)
Big4 = big.NewInt(4)
Big32 = big.NewInt(32)
Big256 = big.NewInt(256)
Big257 = big.NewInt(257)
Big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
)

func BigBitsToBits(original *big.Int) *big.Int {
Expand All @@ -43,3 +45,10 @@ func BigBitsArrayToBitsArray(original []*big.Int) []*big.Int {

return bitsArray
}

func MaxBigInt(x, y *big.Int) *big.Int {
if x.Cmp(y) > 0 {
return x
}
return y
}
76 changes: 76 additions & 0 deletions consensus/blake3pow/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
big8 = big.NewInt(8)
big9 = big.NewInt(9)
big10 = big.NewInt(10)
big20 = big.NewInt(20)
big32 = big.NewInt(32)
bigMinus99 = big.NewInt(-99)
big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // 2^256
Expand Down Expand Up @@ -280,11 +281,35 @@ func (blake3pow *Blake3pow) verifyHeader(chain consensus.ChainHeaderReader, head
if common.Big0.Cmp(header.ParentDeltaS()) != 0 {
return fmt.Errorf("invalid parent delta s: have %v, want %v", header.ParentDeltaS(), common.Big0)
}
// If parent block is dom, validate the prime difficulty
if nodeCtx == common.REGION_CTX {
primeEntropyThreshold, err := blake3pow.CalcPrimeEntropyThreshold(chain, parent)
if err != nil {
return err
}
if header.PrimeEntropyThreshold(parent.Location().SubIndex()).Cmp(primeEntropyThreshold) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v", header.PrimeEntropyThreshold(parent.Location().SubIndex()), primeEntropyThreshold)
}
}
} else {
parentDeltaS := blake3pow.DeltaLogS(parent)
if parentDeltaS.Cmp(header.ParentDeltaS()) != 0 {
return fmt.Errorf("invalid parent delta s: have %v, want %v", header.ParentDeltaS(), parentDeltaS)
}
if nodeCtx == common.REGION_CTX {
// if parent is not a dom block, no adjustment to the prime or region difficulty will be made
for i := 0; i < common.NumZonesInRegion; i++ {
if header.PrimeEntropyThreshold(i).Cmp(parent.PrimeEntropyThreshold(i)) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v at index %v", header.PrimeEntropyThreshold(i), parent.PrimeEntropyThreshold(i), i)
}
}
}
if nodeCtx == common.ZONE_CTX {
if header.PrimeEntropyThreshold(common.NodeLocation.Zone()).Cmp(parent.PrimeEntropyThreshold(common.NodeLocation.Zone())) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v at index %v", header.PrimeEntropyThreshold(common.NodeLocation.Zone()), parent.PrimeEntropyThreshold(common.NodeLocation.Zone()), common.NodeLocation.Zone())
}
}

}
}

Expand Down Expand Up @@ -376,6 +401,57 @@ func (blake3pow *Blake3pow) CalcDifficulty(chain consensus.ChainHeaderReader, pa
return x
}

// CalcPrimeDifficultyThreshold calculates the difficulty that a block must meet
// to become a region block. This function needs to have a controller so that the
// liveliness of the slices can balance even if the hash rate of the slice varies.
// This will also cause the production of the prime blocks to naturally diverge
// with time reducing the uncle rate. The controller is built to adjust the
// number of zone blocks it takes to produce a prime block. This is done based on
// the prior number of blocks to reach threshold which is than multiplied by the
// current difficulty to establish the threshold. The controller adjust the block
// threshold value and is a simple form of a bang-bang controller which is all
// that is needed to ensure liveliness of the slices in prime overtime. If the
// slice is not sufficiently lively 20 zone blocks are subtracted from the
// threshold. If it is too lively 20 blocks are added to the threshold.
func (blake3pow *Blake3pow) CalcPrimeEntropyThreshold(chain consensus.ChainHeaderReader, parent *types.Header) (*big.Int, error) {
nodeCtx := common.NodeLocation.Context()

if nodeCtx != common.REGION_CTX {
log.Error("Cannot CalcPrimeEntropyThreshold for", "context", nodeCtx)
return nil, errors.New("cannot CalcPrimeEntropyThreshold for non-region context")
}

if parent.Hash() == chain.Config().GenesisHash {
return parent.PrimeEntropyThreshold(parent.Location().SubIndex()), nil
}

// Get the primeTerminus
termini := chain.GetTerminiByHash(parent.ParentHash())
if termini == nil {
return nil, errors.New("termini not found in CalcPrimeEntropyThreshold")
}
primeTerminusHeader := chain.GetHeaderByHash(termini.PrimeTerminiAtIndex(parent.Location().SubIndex()))

log.Info("CalcPrimeEntropyThreshold", "primeTerminusHeader:", primeTerminusHeader.NumberArray(), "Hash", primeTerminusHeader.Hash())
deltaNumber := new(big.Int).Sub(parent.Number(), primeTerminusHeader.Number())
log.Info("CalcPrimeEntropyThreshold", "deltaNumber:", deltaNumber)
target := new(big.Int).Mul(big.NewInt(common.NumRegionsInPrime), params.TimeFactor)
target = new(big.Int).Mul(big.NewInt(common.NumZonesInRegion), target)
log.Info("CalcPrimeEntropyThreshold", "target:", target)

var newThreshold *big.Int
if target.Cmp(deltaNumber) > 0 {
newThreshold = new(big.Int).Add(parent.PrimeEntropyThreshold(parent.Location().Zone()), big20)
} else {
newThreshold = new(big.Int).Sub(parent.PrimeEntropyThreshold(parent.Location().Zone()), big20)
}
newMinThreshold := new(big.Int).Div(target, big2)
newThreshold = new(big.Int).Set(common.MaxBigInt(newThreshold, newMinThreshold))
log.Info("CalcPrimeEntropyThreshold", "newThreshold:", newThreshold)

return newThreshold, nil
}

func (blake3pow *Blake3pow) IsDomCoincident(chain consensus.ChainHeaderReader, header *types.Header) bool {
_, order, err := blake3pow.CalcOrder(header)
if err != nil {
Expand Down
42 changes: 16 additions & 26 deletions consensus/blake3pow/poem.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,31 @@ func (blake3pow *Blake3pow) CalcOrder(header *types.Header) (*big.Int, int, erro

// Get entropy reduction of this header
intrinsicS := blake3pow.IntrinsicLogS(header.Hash())

// This is the updated the threshold calculation based on the zone difficulty threshold
target := new(big.Int).Div(big2e256, header.Difficulty()).Bytes()
target := new(big.Int).Div(common.Big2e256, header.Difficulty()).Bytes()
zoneThresholdS := blake3pow.IntrinsicLogS(common.BytesToHash(target))
timeFactorHierarchyDepthMultiple := new(big.Int).Mul(params.TimeFactor, big.NewInt(common.HierarchyDepth))

// Prime case
primeEntropyThreshold := new(big.Int).Mul(timeFactorHierarchyDepthMultiple, timeFactorHierarchyDepthMultiple)
primeEntropyThreshold = new(big.Int).Mul(primeEntropyThreshold, zoneThresholdS)
primeBlockThreshold := new(big.Int).Quo(primeEntropyThreshold, big.NewInt(2))
primeEntropyThreshold = new(big.Int).Sub(primeEntropyThreshold, primeBlockThreshold)

primeBlockEntropyThresholdAdder, _ := mathutil.BinaryLog(primeBlockThreshold, 8)
primeBlockEntropyThreshold := new(big.Int).Add(zoneThresholdS, big.NewInt(int64(primeBlockEntropyThresholdAdder)))
// PRIME
// Compute the total accumulated entropy since the last prime block
totalDeltaSPrime := new(big.Int).Add(header.ParentDeltaS(common.REGION_CTX), header.ParentDeltaS(common.ZONE_CTX))
totalDeltaSPrime.Add(totalDeltaSPrime, intrinsicS)

totalDeltaS := new(big.Int).Add(header.ParentDeltaS(common.REGION_CTX), header.ParentDeltaS(common.ZONE_CTX))
totalDeltaS.Add(totalDeltaS, intrinsicS)
if intrinsicS.Cmp(primeBlockEntropyThreshold) > 0 && totalDeltaS.Cmp(primeEntropyThreshold) > 0 {
// PrimeEntropyThreshold number of zone blocks times the intrinsic logs of
// the given header determines the prime block
primeEntropyThreshold := new(big.Int).Mul(zoneThresholdS, header.PrimeEntropyThreshold(header.Location().Zone()))
if totalDeltaSPrime.Cmp(primeEntropyThreshold) > 0 {
return intrinsicS, common.PRIME_CTX, nil
}

// Region case
regionEntropyThreshold := new(big.Int).Mul(timeFactorHierarchyDepthMultiple, zoneThresholdS)
regionBlockThreshold := new(big.Int).Quo(regionEntropyThreshold, big.NewInt(2))
regionEntropyThreshold = new(big.Int).Sub(regionEntropyThreshold, regionBlockThreshold)

regionBlockEntropyThresholdAdder, _ := mathutil.BinaryLog(regionBlockThreshold, 8)
regionBlockEntropyThreshold := new(big.Int).Add(zoneThresholdS, big.NewInt(int64(regionBlockEntropyThresholdAdder)))

totalDeltaS = new(big.Int).Add(header.ParentDeltaS(common.ZONE_CTX), intrinsicS)
if intrinsicS.Cmp(regionBlockEntropyThreshold) > 0 && totalDeltaS.Cmp(regionEntropyThreshold) > 0 {
// REGION
// Compute the total accumulated entropy since the last region block
totalDeltaSRegion := new(big.Int).Add(header.ParentDeltaS(common.ZONE_CTX), intrinsicS)
regionEntropyThreshold := new(big.Int).Mul(zoneThresholdS, params.TimeFactor)
regionEntropyThreshold = new(big.Int).Mul(regionEntropyThreshold, big.NewInt(common.NumZonesInRegion))
if totalDeltaSRegion.Cmp(regionEntropyThreshold) > 0 {
return intrinsicS, common.REGION_CTX, nil
}

// Zone case
// ZONE
return intrinsicS, common.ZONE_CTX, nil
}

Expand Down
6 changes: 6 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type ChainHeaderReader interface {

// GetHeaderByHash retrieves a block header from the database by its hash.
GetHeaderByHash(hash common.Hash) *types.Header

// GetTerminiByHash retrieves the termini for a given header hash
GetTerminiByHash(hash common.Hash) *types.Termini
}

// ChainReader defines a small collection of methods needed to access the local
Expand Down Expand Up @@ -128,6 +131,9 @@ type Engine interface {
// that a new block should have.
CalcDifficulty(chain ChainHeaderReader, parent *types.Header) *big.Int

// CalcPrimeEntropyThreshold is the threshold adjustment algorithm for prime blocks per slice
CalcPrimeEntropyThreshold(chain ChainHeaderReader, parent *types.Header) (*big.Int, error)

// IsDomCoincident returns true if this block satisfies the difficulty order
// of a dominant chain. If this node does not have a dominant chain (i.e.
// if this is a prime node), then the function will always return false.
Expand Down
78 changes: 78 additions & 0 deletions consensus/progpow/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ var (
big8 = big.NewInt(8)
big9 = big.NewInt(9)
big10 = big.NewInt(10)
big20 = big.NewInt(20)
big32 = big.NewInt(32)
big100 = big.NewInt(100)
bigMinus99 = big.NewInt(-99)
big2e256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // 2^256
)
Expand Down Expand Up @@ -280,11 +282,36 @@ func (progpow *Progpow) verifyHeader(chain consensus.ChainHeaderReader, header,
if common.Big0.Cmp(header.ParentDeltaS()) != 0 {
return fmt.Errorf("invalid parent delta s: have %v, want %v", header.ParentDeltaS(), common.Big0)
}
// If parent block is dom, validate the prime difficulty
if nodeCtx == common.REGION_CTX {
primeEntropyThreshold, err := progpow.CalcPrimeEntropyThreshold(chain, parent)
if err != nil {
return err
}
if header.PrimeEntropyThreshold(parent.Location().SubIndex()).Cmp(primeEntropyThreshold) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v", header.PrimeEntropyThreshold(parent.Location().SubIndex()), primeEntropyThreshold)
}
}
} else {
parentDeltaS := progpow.DeltaLogS(parent)
if parentDeltaS.Cmp(header.ParentDeltaS()) != 0 {
return fmt.Errorf("invalid parent delta s: have %v, want %v", header.ParentDeltaS(), parentDeltaS)
}

if nodeCtx == common.REGION_CTX {
// if parent is not a dom block, no adjustment to the prime or region difficulty will be made
for i := 0; i < common.NumZonesInRegion; i++ {
if header.PrimeEntropyThreshold(i).Cmp(parent.PrimeEntropyThreshold(i)) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v at index %v", header.PrimeEntropyThreshold(i), parent.PrimeEntropyThreshold(i), i)
}
}
}
if nodeCtx == common.ZONE_CTX {
if header.PrimeEntropyThreshold(common.NodeLocation.Zone()).Cmp(parent.PrimeEntropyThreshold(common.NodeLocation.Zone())) != 0 {
return fmt.Errorf("invalid prime difficulty pd: have %v, want %v at index %v", header.PrimeEntropyThreshold(common.NodeLocation.Zone()), parent.PrimeEntropyThreshold(common.NodeLocation.Zone()), common.NodeLocation.Zone())
}
}

}
}
if nodeCtx == common.ZONE_CTX {
Expand Down Expand Up @@ -320,6 +347,57 @@ func (progpow *Progpow) verifyHeader(chain consensus.ChainHeaderReader, header,
return nil
}

// CalcPrimeDifficultyThreshold calculates the difficulty that a block must meet
// to become a region block. This function needs to have a controller so that the
// liveliness of the slices can balance even if the hash rate of the slice varies.
// This will also cause the production of the prime blocks to naturally diverge
// with time reducing the uncle rate. The controller is built to adjust the
// number of zone blocks it takes to produce a prime block. This is done based on
// the prior number of blocks to reach threshold which is than multiplied by the
// current difficulty to establish the threshold. The controller adjust the block
// threshold value and is a simple form of a bang-bang controller which is all
// that is needed to ensure liveliness of the slices in prime overtime. If the
// slice is not sufficiently lively 20 zone blocks are subtracted from the
// threshold. If it is too lively 20 blocks are added to the threshold.
func (progpow *Progpow) CalcPrimeEntropyThreshold(chain consensus.ChainHeaderReader, parent *types.Header) (*big.Int, error) {
nodeCtx := common.NodeLocation.Context()

if nodeCtx != common.REGION_CTX {
log.Error("Cannot CalcPrimeEntropyThreshold for", "context", nodeCtx)
return nil, errors.New("cannot CalcPrimeEntropyThreshold for non-region context")
}

if parent.Hash() == chain.Config().GenesisHash {
return parent.PrimeEntropyThreshold(parent.Location().SubIndex()), nil
}

// Get the primeTerminus
termini := chain.GetTerminiByHash(parent.ParentHash())
if termini == nil {
return nil, errors.New("termini not found in CalcPrimeEntropyThreshold")
}
primeTerminusHeader := chain.GetHeaderByHash(termini.PrimeTerminiAtIndex(parent.Location().SubIndex()))

log.Info("CalcPrimeEntropyThreshold", "primeTerminusHeader:", primeTerminusHeader.NumberArray(), "Hash", primeTerminusHeader.Hash())
deltaNumber := new(big.Int).Sub(parent.Number(), primeTerminusHeader.Number())
log.Info("CalcPrimeEntropyThreshold", "deltaNumber:", deltaNumber)
target := new(big.Int).Mul(big.NewInt(common.NumRegionsInPrime), params.TimeFactor)
target = new(big.Int).Mul(big.NewInt(common.NumZonesInRegion), target)
log.Info("CalcPrimeEntropyThreshold", "target:", target)

var newThreshold *big.Int
if target.Cmp(deltaNumber) > 0 {
newThreshold = new(big.Int).Add(parent.PrimeEntropyThreshold(parent.Location().Zone()), big20)
} else {
newThreshold = new(big.Int).Sub(parent.PrimeEntropyThreshold(parent.Location().Zone()), big20)
}
newMinThreshold := new(big.Int).Div(target, big2)
newThreshold = new(big.Int).Set(common.MaxBigInt(newThreshold, newMinThreshold))
log.Info("CalcPrimeEntropyThreshold", "newThreshold:", newThreshold)

return newThreshold, nil
}

// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
Expand Down
40 changes: 15 additions & 25 deletions consensus/progpow/poem.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,30 @@ func (progpow *Progpow) CalcOrder(header *types.Header) (*big.Int, int, error) {

// Get entropy reduction of this header
intrinsicS := progpow.IntrinsicLogS(powHash)

// This is the updated the threshold calculation based on the zone difficulty threshold
target := new(big.Int).Div(big2e256, header.Difficulty()).Bytes()
target := new(big.Int).Div(common.Big2e256, header.Difficulty()).Bytes()
zoneThresholdS := progpow.IntrinsicLogS(common.BytesToHash(target))
timeFactorHierarchyDepthMultiple := new(big.Int).Mul(params.TimeFactor, big.NewInt(common.HierarchyDepth))

// Prime case
primeEntropyThreshold := new(big.Int).Mul(timeFactorHierarchyDepthMultiple, timeFactorHierarchyDepthMultiple)
primeEntropyThreshold = new(big.Int).Mul(primeEntropyThreshold, zoneThresholdS)
primeBlockThreshold := new(big.Int).Quo(primeEntropyThreshold, big.NewInt(2))
primeEntropyThreshold = new(big.Int).Sub(primeEntropyThreshold, primeBlockThreshold)

primeBlockEntropyThresholdAdder, _ := mathutil.BinaryLog(primeBlockThreshold, 8)
primeBlockEntropyThreshold := new(big.Int).Add(zoneThresholdS, big.NewInt(int64(primeBlockEntropyThresholdAdder)))
// PRIME
// Compute the total accumulated entropy since the last prime block
totalDeltaSPrime := new(big.Int).Add(header.ParentDeltaS(common.REGION_CTX), header.ParentDeltaS(common.ZONE_CTX))
totalDeltaSPrime.Add(totalDeltaSPrime, intrinsicS)

totalDeltaS := new(big.Int).Add(header.ParentDeltaS(common.REGION_CTX), header.ParentDeltaS(common.ZONE_CTX))
totalDeltaS.Add(totalDeltaS, intrinsicS)
if intrinsicS.Cmp(primeBlockEntropyThreshold) > 0 && totalDeltaS.Cmp(primeEntropyThreshold) > 0 {
// PrimeEntropyThreshold number of zone blocks times the intrinsic logs of the given header determines the prime block
primeEntropyThreshold := new(big.Int).Mul(zoneThresholdS, header.PrimeEntropyThreshold(header.Location().Zone()))
if totalDeltaSPrime.Cmp(primeEntropyThreshold) > 0 {
return intrinsicS, common.PRIME_CTX, nil
}

// Region case
regionEntropyThreshold := new(big.Int).Mul(timeFactorHierarchyDepthMultiple, zoneThresholdS)
regionBlockThreshold := new(big.Int).Quo(regionEntropyThreshold, big.NewInt(2))
regionEntropyThreshold = new(big.Int).Sub(regionEntropyThreshold, regionBlockThreshold)

regionBlockEntropyThresholdAdder, _ := mathutil.BinaryLog(regionBlockThreshold, 8)
regionBlockEntropyThreshold := new(big.Int).Add(zoneThresholdS, big.NewInt(int64(regionBlockEntropyThresholdAdder)))

totalDeltaS = new(big.Int).Add(header.ParentDeltaS(common.ZONE_CTX), intrinsicS)
if intrinsicS.Cmp(regionBlockEntropyThreshold) > 0 && totalDeltaS.Cmp(regionEntropyThreshold) > 0 {
// REGION
// Compute the total accumulated entropy since the last region block
totalDeltaSRegion := new(big.Int).Add(header.ParentDeltaS(common.ZONE_CTX), intrinsicS)
regionEntropyThreshold := new(big.Int).Mul(zoneThresholdS, params.TimeFactor)
regionEntropyThreshold = new(big.Int).Mul(regionEntropyThreshold, big.NewInt(common.NumZonesInRegion))
if totalDeltaSRegion.Cmp(regionEntropyThreshold) > 0 {
return intrinsicS, common.REGION_CTX, nil
}

// ZONE
return intrinsicS, common.ZONE_CTX, nil
}

Expand Down
1 change: 1 addition & 0 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,4 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (cr *fakeChainReader) GetTerminiByHash(hash common.Hash) *types.Termini { return nil }
Loading

0 comments on commit 4cc39ac

Please sign in to comment.