primitives: Add work calc from diff bits.

This implements code for calculating a uint256 work value from
difficulty bits along with associated tests.

The function is the semantic equivalent of CalcWork from
blockchain/standalone updated to use and return the new uint256 type
instead of stdlib big integers.

Note that the original calculation involves a dividend of 2^256 which is
not directly representable by a uint256, so this implementation retains
the same semantics by transforming the calculation as described in
detail by the comments.
This commit is contained in:
Dave Collins 2021-11-07 16:29:28 -06:00
parent 3e7115cf64
commit ecf6c6313e
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
2 changed files with 93 additions and 0 deletions

View File

@ -141,3 +141,50 @@ func Uint256ToDiffBits(n *uint256.Uint256) uint32 {
const isNegative = false
return uint256ToDiffBits(n, isNegative)
}
// CalcWork calculates a work value from difficulty bits. Decred 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
// DiffBitsToUint256. The main chain is selected by choosing the chain that has
// the most proof of work (highest difficulty). 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. For legacy reasons, the
// result is zero when the difficulty is zero. Finally, to avoid really small
// floating point numbers, the result multiplies the numerator by 2^256 and adds
// 1 to the denominator.
func CalcWork(diffBits uint32) uint256.Uint256 {
// Return a work value of zero if the passed difficulty bits represent a
// negative number, a number that overflows a uint256, or zero. Note this
// should not happen in practice with valid blocks, but an invalid block
// could trigger it.
diff, isNegative, overflows := DiffBitsToUint256(diffBits)
if isNegative || overflows || diff.IsZero() {
return uint256.Uint256{}
}
// The goal is to calculate 2^256 / (diff+1), where diff > 0 using a
// fixed-precision uint256.
//
// Since 2^256 can't be represented by a uint256, the calc is performed as
// follows:
//
// Notice:
// work = (2^256 / (diff+1))
// => work = ((2^256-diff-1) / (diff+1))+1
//
// Next, observe that 2^256-diff-1 is the one's complement of diff as a
// uint256 which is equivalent to the bitwise not. Also, of special note is
// the case when diff = 2^256-1 because (2^256-1)+1 ≡ 0 (mod 2^256) and
// thus would result in division by zero when working with a uint256. The
// original calculation would produce 1 in that case, so the resulting
// piecewise function is:
//
// {work = 1 , where diff = 2^256-1
// {work = (^diff / (diff+1))+1, where 0 < diff < 2^256-1
//
// However, a difficulty target of 2^256 - 1 is impossible to encode in the
// difficulty bits, so it is safe to ignore that case.
divisor := new(uint256.Uint256).SetUint64(1).Add(&diff)
return *diff.Not().Div(divisor).AddUint64(1)
}

View File

@ -209,3 +209,49 @@ func TestUint256ToDiffBits(t *testing.T) {
}
}
}
// TestCalcWork ensures calculating a work value from a compact target
// difficulty produces the correct results.
func TestCalcWork(t *testing.T) {
t.Parallel()
tests := []struct {
name string // test description
input uint32 // target difficulty bits to test
want string // expected uint256
}{{
name: "mainnet block 1",
input: 0x1b01ffff,
want: "0000000000000000000000000000000000000000000000000000800040002000",
}, {
name: "mainnet block 288",
input: 0x1b01330e,
want: "0000000000000000000000000000000000000000000000000000d56f2dcbe105",
}, {
name: "higher diff (exponent 24)",
input: 0x185fb28a,
want: "000000000000000000000000000000000000000000000002acd33ddd458512da",
}, {
name: "zero",
input: 0,
want: "0000000000000000000000000000000000000000000000000000000000000000",
}, {
name: "max uint256",
input: 0x2100ffff,
want: "0000000000000000000000000000000000000000000000000000000000000001",
}, {
name: "negative target difficulty",
input: 0x1810000,
want: "0000000000000000000000000000000000000000000000000000000000000000",
}}
for _, test := range tests {
want := hexToUint256(test.want)
result := CalcWork(test.input)
if !result.Eq(want) {
t.Errorf("%q: mismatched result -- got %x, want %x", test.name,
result, want)
continue
}
}
}