Skip to content

Commit

Permalink
multi: Separate block hash and proof of work hash.
Browse files Browse the repository at this point in the history
This separates the notion of the block hash from the proof of work hash
so the proof of work hashing function can independently be changed
without affecting anything else that relies on the block hash as
generated by blake256r14.
  • Loading branch information
davecgh committed Jun 6, 2023
1 parent 3e621ba commit dbb6b78
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 70 deletions.
10 changes: 4 additions & 6 deletions blockchain/chaingen/generator.go
@@ -1,4 +1,4 @@
// Copyright (c) 2016-2021 The Decred developers
// Copyright (c) 2016-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -1508,7 +1508,7 @@ func compactToBig(compact uint32) *big.Int {
// state.
func (g *Generator) IsSolved(header *wire.BlockHeader) bool {
targetDifficulty := compactToBig(header.Bits)
hash := header.BlockHash()
hash := header.PowHashV1()
return hashToBig(&hash).Cmp(targetDifficulty) <= 0
}

Expand Down Expand Up @@ -1542,10 +1542,8 @@ func (g *Generator) solveBlock(header *wire.BlockHeader) bool {
return
default:
hdr.Nonce = i
hash := hdr.BlockHash()
if hashToBig(&hash).Cmp(
targetDifficulty) <= 0 {

hash := hdr.PowHashV1()
if hashToBig(&hash).Cmp(targetDifficulty) <= 0 {
results <- sbResult{true, i}
return
}
Expand Down
5 changes: 4 additions & 1 deletion blockchain/go.mod
Expand Up @@ -22,4 +22,7 @@ require (
github.com/decred/slog v1.2.0 // indirect
)

replace github.com/decred/dcrd/chaincfg/v3 => ../chaincfg
replace (
github.com/decred/dcrd/chaincfg/v3 => ../chaincfg
github.com/decred/dcrd/wire => ../wire
)
2 changes: 0 additions & 2 deletions blockchain/go.sum
Expand Up @@ -23,7 +23,5 @@ github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5
github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M=
github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58=
github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8=
github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg=
github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w=
github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM=
github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0=
18 changes: 9 additions & 9 deletions blockchain/standalone/pow.go
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -182,20 +182,20 @@ func CheckProofOfWorkRange(difficultyBits uint32, powLimit *big.Int) error {
return checkProofOfWorkRange(target, powLimit)
}

// CheckProofOfWork ensures the provided block hash is less than the provided
// compact target difficulty and that the target difficulty is in min/max range
// per the provided proof-of-work limit.
func CheckProofOfWork(blockHash *chainhash.Hash, difficultyBits uint32, powLimit *big.Int) error {
// CheckProofOfWork ensures the provided hash is less than the provided compact
// target difficulty and that the target difficulty is in min/max range per the
// provided proof-of-work limit.
func CheckProofOfWork(powHash *chainhash.Hash, difficultyBits uint32, powLimit *big.Int) error {
target := CompactToBig(difficultyBits)
if err := checkProofOfWorkRange(target, powLimit); err != nil {
return err
}

// The block hash must be less than the target difficulty.
hashNum := HashToBig(blockHash)
// The proof of work hash must be less than the target difficulty.
hashNum := HashToBig(powHash)
if hashNum.Cmp(target) > 0 {
str := fmt.Sprintf("block hash of %064x is higher than expected max "+
"of %064x", hashNum, target)
str := fmt.Sprintf("proof of work hash %064x is higher than "+
"expected max of %064x", hashNum, target)
return ruleError(ErrHighHash, str)
}

Expand Down
11 changes: 5 additions & 6 deletions blockchain/standalone/pow_test.go
Expand Up @@ -278,24 +278,23 @@ func TestCheckProofOfWorkRange(t *testing.T) {
}
}

// TestCheckProofOfWorkRange ensures hashes and target difficulties that are
// outside of the acceptable ranges are detected as an error and those inside
// are not.
// TestCheckProofOfWork ensures hashes and target difficulties that are outside
// of the acceptable ranges are detected as an error and those inside are not.
func TestCheckProofOfWork(t *testing.T) {
tests := []struct {
name string // test description
hash string // block hash to test
hash string // proof of work hash to test
bits uint32 // compact target difficulty bits to test
powLimit string // proof of work limit
err error // expected error
}{{
name: "mainnet block 1 hash",
name: "mainnet block 1 pow hash",
hash: "000000000000437482b6d47f82f374cde539440ddb108b0a76886f0d87d126b9",
bits: 0x1b01ffff,
powLimit: mockMainNetPowLimit(),
err: nil,
}, {
name: "mainnet block 288 hash",
name: "mainnet block 288 pow hash",
hash: "000000000000e0ab546b8fc19f6d94054d47ffa5fe79e17611d170662c8b702b",
bits: 0x1b01330e,
powLimit: mockMainNetPowLimit(),
Expand Down
46 changes: 33 additions & 13 deletions internal/blockchain/blockindex.go
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2018-2022 The Decred developers
// Copyright (c) 2018-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -440,7 +440,7 @@ func compareHashesAsUint256LE(a, b *chainhash.Hash) int {
// 1. More total cumulative work
// 2. Having block data available
// 3. Receiving data earlier
// 4. Hash that represents more work (smaller value as a little-endian uint256)
// 4. Hash that represents a smaller value as a little-endian uint256
//
// This function MUST be called with the block index lock held (for reads).
func betterCandidate(a, b *blockNode) bool {
Expand Down Expand Up @@ -479,9 +479,17 @@ func betterCandidate(a, b *blockNode) bool {
// can be the same when the block data for a given header is not yet known
// as well.
//
// Note that it is more difficult to find hashes with more leading zeros
// when treated as a little-endian uint256, so smaller values represent more
// work and are therefore better candidates.
// Note that this logic was originally based on the block hash and proof of
// work hash being the same and the fact that it is more difficult to find
// hashes with more leading zeros when treated as a little-endian uint256,
// meaning that smaller values represent more work and were therefore
// considered better candidates.
//
// While the initial motivation no longer applies now that the block hash
// and proof of work hash are independent, it is still necessary to break
// the tie to have a stable order and continuing to consider hashes with
// smaller values when treated as a little-endian uint256 as better
// candidates serves that purpose well.
return compareHashesAsUint256LE(&a.hash, &b.hash) < 0
}

Expand Down Expand Up @@ -625,14 +633,26 @@ func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool {
// as a key in the block index.
func shortBlockKey(hash *chainhash.Hash) uint32 {
// Use the first bytes of the hash directly since it is the result of a hash
// function that produces a uniformly-random distribution. It is also worth
// noting that the mining process reduces entropy by zeroing the bits at the
// other end of the array, but there would need to be effectively impossible
// to achieve hash rates exceeding ~2^215.77 hashes/sec (aka ~89.8 peta
// yotta yotta hashes/sec) in order to start zeroing out the bits used here.
// Not only is that impossible for all intents and purposes, the only effect
// would be reducing overall memory savings due to increased collisions
// among the shortened keys.
// function that produces a uniformly-random distribution.
//
// It is also worth noting that the choice of bytes was made based on the
// possibility of the block hash and proof of work hash being the same and
// considering that the mining process reduces entropy by zeroing the bits
// at the other end of the array.
//
// Since the block hash and proof of work hash are independent, the entropy
// concern may or may not be relevant dependening on whether or not the same
// hash function is used (or was ever used historically in the case of hash
// algorithm changes), but this choice of bytes covers both possibilities
// without any notable downside.
//
// For the case when the block hash and proof of work hash are the same,
// there would need to be effectively impossible to achieve hash rates
// exceeding ~2^215.77 hashes/sec (aka ~89.8 peta yotta yotta hashes/sec) in
// order to start zeroing out the bits used here. Not only is that
// impossible for all intents and purposes, the only effect would be
// reducing overall memory savings due to increased collisions among the
// shortened keys.
return binary.BigEndian.Uint32(hash[0:4])
}

Expand Down
6 changes: 3 additions & 3 deletions internal/blockchain/validate.go
Expand Up @@ -708,9 +708,9 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio
//
// - The target difficulty must be larger than zero.
// - The target difficulty must be less than the maximum allowed.
// - The block hash must be less than the claimed target.
blockHash := header.BlockHash()
err := standalone.CheckProofOfWork(&blockHash, header.Bits, powLimit)
// - The proof of work hash must be less than the claimed target.
powHash := header.PowHashV1()
err := standalone.CheckProofOfWork(&powHash, header.Bits, powLimit)
return standaloneToChainRuleError(err)
}

Expand Down
17 changes: 9 additions & 8 deletions internal/mining/cpuminer/cpuminer.go
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/crypto/blake256"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/internal/blockchain"
"github.com/decred/dcrd/internal/mining"
Expand Down Expand Up @@ -222,14 +223,14 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
}

// The block was accepted.
coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut
coinbaseTxGenerated := int64(0)
for _, out := range coinbaseTxOuts {
coinbaseTxGenerated += out.Value
blockHash := block.Hash()
var powHashStr string
powHash := block.MsgBlock().PowHashV1()
if powHash != *blockHash {
powHashStr = ", pow hash " + powHash.String()
}
log.Infof("Block submitted via CPU miner accepted (hash %s, height %v, "+
"amount %v)", block.Hash(), block.Height(),
dcrutil.Amount(coinbaseTxGenerated))
log.Infof("Block submitted via CPU miner accepted (hash %s, height %d%s)",
blockHash, block.Height(), powHashStr)
return true
}

Expand Down Expand Up @@ -327,7 +328,7 @@ func (m *CPUMiner) solveBlock(ctx context.Context, header *wire.BlockHeader, sta
// compute the block header hash.
const nonceSerOffset = 140
littleEndian.PutUint32(hdrBytes[nonceSerOffset:], nonce)
hash := chainhash.HashH(hdrBytes)
hash := chainhash.Hash(blake256.Sum256(hdrBytes))
hashesCompleted++

// The block is solved when the new block hash is less than the
Expand Down
16 changes: 11 additions & 5 deletions internal/rpcserver/rpcserver.go
Expand Up @@ -3854,9 +3854,10 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte
return false, rpcInvalidError("Invalid block header: %v", err)
}

// Ensure the submitted block hash is less than the target difficulty.
blockHash := submittedHeader.BlockHash()
err = standalone.CheckProofOfWork(&blockHash, submittedHeader.Bits,
// Ensure the submitted proof of work hash is less than the target
// difficulty.
powHash := submittedHeader.PowHashV1()
err = standalone.CheckProofOfWork(&powHash, submittedHeader.Bits,
s.cfg.ChainParams.PowLimit)
if err != nil {
// Anything other than a rule violation is an unexpected error, so
Expand Down Expand Up @@ -3916,8 +3917,13 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte
}

// The block was accepted.
log.Infof("Block submitted via getwork accepted: %s (height %d)",
block.Hash(), msgBlock.Header.Height)
var powHashStr string
blockHash := block.Hash()
if *blockHash != powHash {
powHashStr = ", pow hash " + powHash.String()
}
log.Infof("Block submitted via getwork accepted: %s (height %d%s)",
blockHash, msgBlock.Header.Height, powHashStr)
return true, nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/rpcserver/rpcserverhandlers_test.go
Expand Up @@ -5831,7 +5831,7 @@ func TestHandleGetWork(t *testing.T) {
// Create an orphan block by mutating the previous block field
// and solving the block.
isSolved := func(header *wire.BlockHeader) bool {
powHash := header.BlockHash()
powHash := header.PowHashV1()
err := standalone.CheckProofOfWork(&powHash, header.Bits,
mockPowLimitBig)
return err == nil
Expand Down
20 changes: 10 additions & 10 deletions internal/staging/primitives/pow.go
@@ -1,4 +1,4 @@
// Copyright (c) 2021-2022 The Decred developers
// Copyright (c) 2021-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -217,7 +217,7 @@ func checkProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) (uint256.
}
if overflows {
str := fmt.Sprintf("target difficulty bits %08x is higher than the "+
"max limit %x", diffBits, powLimit)
"max limit %064x", diffBits, powLimit)
return uint256.Uint256{}, ruleError(ErrUnexpectedDifficulty, str)
}
if target.IsZero() {
Expand All @@ -227,7 +227,7 @@ func checkProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) (uint256.

// The target difficulty must not exceed the maximum allowed.
if target.Gt(powLimit) {
str := fmt.Sprintf("target difficulty of %x is higher than max of %x",
str := fmt.Sprintf("target difficulty %064x is higher than max %064x",
target, powLimit)
return uint256.Uint256{}, ruleError(ErrUnexpectedDifficulty, str)
}
Expand All @@ -243,20 +243,20 @@ func CheckProofOfWorkRange(diffBits uint32, powLimit *uint256.Uint256) error {
return err
}

// CheckProofOfWork ensures the provided block hash is less than the target
// difficulty represented by given header bits and that said difficulty is in
// min/max range per the provided proof-of-work limit.
func CheckProofOfWork(blockHash *chainhash.Hash, diffBits uint32, powLimit *uint256.Uint256) error {
// CheckProofOfWork ensures the provided hash is less than the target difficulty
// represented by given header bits and that said difficulty is in min/max range
// per the provided proof-of-work limit.
func CheckProofOfWork(powHash *chainhash.Hash, diffBits uint32, powLimit *uint256.Uint256) error {
target, err := checkProofOfWorkRange(diffBits, powLimit)
if err != nil {
return err
}

// The block hash must be less than the target difficulty.
hashNum := HashToUint256(blockHash)
hashNum := HashToUint256(powHash)
if hashNum.Gt(&target) {
str := fmt.Sprintf("block hash of %x is higher than expected max of %x",
hashNum, target)
str := fmt.Sprintf("proof of work hash %064x is higher than expected "+
"max of %064x", hashNum, target)
return ruleError(ErrHighHash, str)
}

Expand Down
15 changes: 10 additions & 5 deletions wire/blockheader.go
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -89,16 +89,21 @@ const blockHeaderLen = 180

// BlockHash computes the block identifier hash for the given block header.
func (h *BlockHeader) BlockHash() chainhash.Hash {
// Encode the header and hash256 everything prior to the number of
// transactions. Ignore the error returns since there is no way the
// encode could fail except being out of memory which would cause a
// run-time panic.
// Encode the header and hash everything prior to the number of
// transactions. Ignore the error returns since there is no way the encode
// could fail except being out of memory which would cause a run-time panic.
buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload))
_ = writeBlockHeader(buf, 0, h)

return chainhash.HashH(buf.Bytes())
}

// PowHashV1 calculates and returns the version 1 proof of work hash for the
// block header.
func (h *BlockHeader) PowHashV1() chainhash.Hash {
return h.BlockHash()
}

// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
// See Deserialize for decoding block headers stored to disk, such as in a
Expand Down
8 changes: 7 additions & 1 deletion wire/msgblock.go
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -359,6 +359,12 @@ func (msg *MsgBlock) BlockHash() chainhash.Hash {
return msg.Header.BlockHash()
}

// PowHashV1 calculates and returns the version 1 proof of work hash for the
// block.
func (msg *MsgBlock) PowHashV1() chainhash.Hash {
return msg.Header.PowHashV1()
}

// TxHashes returns a slice of hashes of all of transactions in this block.
func (msg *MsgBlock) TxHashes() []chainhash.Hash {
hashList := make([]chainhash.Hash, 0, len(msg.Transactions))
Expand Down

0 comments on commit dbb6b78

Please sign in to comment.