kaspad/util/difficulty/difficulty.go
Elichai Turkel 68bd8330ac
Log the networks hashrate (#1406)
* Log the hashrate of each block

* Add a test for GetHashrateString

* Move difficulty related functions to its own package

* Convert the validated log in validateAndInsertBlock to a log function

* Add tests for max/min int
2021-01-13 12:51:23 +02:00

179 lines
6.3 KiB
Go

package difficulty
import (
"math/big"
"time"
)
var (
// bigOne is 1 represented as a big.Int. It is defined here to avoid
// the overhead of creating it multiple times.
bigOne = big.NewInt(1)
// oneLsh256 is 1 shifted left 256 bits. It is defined here to avoid
// the overhead of creating it multiple times.
oneLsh256 = new(big.Int).Lsh(bigOne, 256)
)
// CompactToBig converts a compact representation of a whole number N to an
// unsigned 32-bit number. The representation is similar to IEEE754 floating
// point numbers.
//
// Like IEEE754 floating point, there are three basic components: the sign,
// the exponent, and the mantissa. They are broken out as follows:
//
// * the most significant 8 bits represent the unsigned base 256 exponent
// * bit 23 (the 24th bit) represents the sign bit
// * the least significant 23 bits represent the mantissa
//
// -------------------------------------------------
// | Exponent | Sign | Mantissa |
// -------------------------------------------------
// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
// -------------------------------------------------
//
// The formula to calculate N is:
// N = (-1^sign) * mantissa * 256^(exponent-3)
func CompactToBig(compact uint32) *big.Int {
destination := big.NewInt(0)
CompactToBigWithDestination(compact, destination)
return destination
}
// CompactToBigWithDestination is a version of CompactToBig that
// takes a destination parameter. This is useful for saving memory,
// as then the destination big.Int can be reused.
// See CompactToBig for further details.
func CompactToBigWithDestination(compact uint32, destination *big.Int) {
// Extract the mantissa, sign bit, and exponent.
mantissa := compact & 0x007fffff
isNegative := compact&0x00800000 != 0
exponent := uint(compact >> 24)
// Since the base for the exponent is 256, the exponent can be treated
// as the number of bytes to represent the full 256-bit number. So,
// treat the exponent as the number of bytes and shift the mantissa
// right or left accordingly. This is equivalent to:
// N = mantissa * 256^(exponent-3)
if exponent <= 3 {
mantissa >>= 8 * (3 - exponent)
destination.SetInt64(int64(mantissa))
} else {
destination.SetInt64(int64(mantissa))
destination.Lsh(destination, 8*(exponent-3))
}
// Make it negative if the sign bit is set.
if isNegative {
destination.Neg(destination)
}
}
// BigToCompact converts a whole number N to a compact representation using
// an unsigned 32-bit number. The compact representation only provides 23 bits
// of precision, so values larger than (2^23 - 1) only encode the most
// significant digits of the number. See CompactToBig for details.
func BigToCompact(n *big.Int) uint32 {
// No need to do any work if it's zero.
if n.Sign() == 0 {
return 0
}
// Since the base for the exponent is 256, the exponent can be treated
// as the number of bytes. So, shift the number right or left
// accordingly. This is equivalent to:
// mantissa = mantissa / 256^(exponent-3)
var mantissa uint32
exponent := uint(len(n.Bytes()))
if exponent <= 3 {
mantissa = uint32(n.Bits()[0])
mantissa <<= 8 * (3 - exponent)
} else {
// Use a copy to avoid modifying the caller's original number.
tn := new(big.Int).Set(n)
mantissa = uint32(tn.Rsh(tn, 8*(exponent-3)).Bits()[0])
}
// When the mantissa already has the sign bit set, the number is too
// large to fit into the available 23-bits, so divide the number by 256
// and increment the exponent accordingly.
if mantissa&0x00800000 != 0 {
mantissa >>= 8
exponent++
}
// Pack the exponent, sign bit, and mantissa into an unsigned 32-bit
// int and return it.
compact := uint32(exponent<<24) | mantissa
if n.Sign() < 0 {
compact |= 0x00800000
}
return compact
}
// CalcWork calculates a work value from difficulty bits. Kaspa increases
// the difficulty for generating a block by decreasing the value which the
// generated hash must be less than. This difficulty target is stored in each
// block header using a compact representation as described in the documentation
// for CompactToBig. Since a lower target difficulty value equates to higher
// actual difficulty, the work value which will be accumulated must be the
// inverse of the difficulty. Also, in order to avoid potential division by
// zero and really small floating point numbers, the result adds 1 to the
// denominator and multiplies the numerator by 2^256.
func CalcWork(bits uint32) *big.Int {
// Return a work value of zero if the passed difficulty bits represent
// a negative number. Note this should not happen in practice with valid
// blocks, but an invalid block could trigger it.
difficultyNum := CompactToBig(bits)
if difficultyNum.Sign() <= 0 {
return big.NewInt(0)
}
// (1 << 256) / (difficultyNum + 1)
denominator := new(big.Int).Add(difficultyNum, bigOne)
return new(big.Int).Div(oneLsh256, denominator)
}
func getHashrate(target *big.Int, TargetTimePerBlock time.Duration) *big.Int {
// From: https://bitcoin.stackexchange.com/a/5557/40800
// difficulty = hashrate / (2^256 / max_target / block_rate_in_seconds)
// hashrate = difficulty * (2^256 / max_target / block_rate_in_seconds)
// difficulty = max_target / target
// hashrate = (max_target / target) * (2^256 / max_target / block_rate_in_seconds)
// hashrate = 2^256 / (target * block_rate_in_seconds)
tmp := new(big.Int)
divisor := new(big.Int).Set(target)
divisor.Mul(divisor, tmp.SetInt64(TargetTimePerBlock.Milliseconds()))
divisor.Div(divisor, tmp.SetInt64(int64(time.Second/time.Millisecond))) // Scale it up to seconds.
divisor.Div(oneLsh256, divisor)
return divisor
}
// GetHashrateString returns the expected hashrate of the network on a certain difficulty target.
func GetHashrateString(target *big.Int, TargetTimePerBlock time.Duration) string {
hashrate := getHashrate(target, TargetTimePerBlock)
in := hashrate.Text(10)
var postfix string
switch {
case len(in) <= 3:
return in + " H/s"
case len(in) <= 6:
postfix = " KH/s"
case len(in) <= 9:
postfix = " MH/s"
case len(in) <= 12:
postfix = " GH/s"
case len(in) <= 15:
postfix = " TH/s"
case len(in) <= 18:
postfix = " PH/s"
case len(in) <= 21:
postfix = " EH/s"
default:
return in + " H/s"
}
highPrecision := len(in) - ((len(in)-1)/3)*3
return in[:highPrecision] + "." + in[highPrecision:highPrecision+2] + postfix
}