From ecf6c6313e25a2ca556e8bfec77d1c0c60698fbd Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sun, 7 Nov 2021 16:29:28 -0600 Subject: [PATCH] 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. --- internal/staging/primitives/pow.go | 47 +++++++++++++++++++++++++ internal/staging/primitives/pow_test.go | 46 ++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/internal/staging/primitives/pow.go b/internal/staging/primitives/pow.go index 820481c5..437d448c 100644 --- a/internal/staging/primitives/pow.go +++ b/internal/staging/primitives/pow.go @@ -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) +} diff --git a/internal/staging/primitives/pow_test.go b/internal/staging/primitives/pow_test.go index 75ea0826..b5eb59d1 100644 --- a/internal/staging/primitives/pow_test.go +++ b/internal/staging/primitives/pow_test.go @@ -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 + } + } +}