Skip to content

Commit 4568f76

Browse files
jrickdavecgh
authored andcommitted
wire: Optimize writes to hashers
This adds two additional type cases to the optimized writes for BLAKE-256 (used by transactions and the original PoW algorithm) and BLAKE-3 (used by the current PoW algorithm). It also updates both the block header and transaction code to use these hashers during the calculation of block and transaction hashes.
1 parent ae9e4e8 commit 4568f76

File tree

5 files changed

+89
-45
lines changed

5 files changed

+89
-45
lines changed

wire/bench_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/decred/dcrd/chaincfg/chainhash"
17+
"github.com/decred/dcrd/crypto/blake256"
1718
)
1819

1920
// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for
@@ -634,6 +635,28 @@ func BenchmarkTxHash(b *testing.B) {
634635
}
635636
}
636637

638+
// BenchmarkTxHashReuseHasher performs a benchmark on how long it takes to hash a
639+
// transaction by reusing the *blake256.Hasher256 object.
640+
func BenchmarkTxHashReuseHasher(b *testing.B) {
641+
h := blake256.NewHasher256()
642+
643+
txHash := func(h *blake256.Hasher256, tx *MsgTx) chainhash.Hash {
644+
txCopy := *tx
645+
txCopy.SerType = TxSerializeNoWitness
646+
err := txCopy.Serialize(h)
647+
if err != nil {
648+
panic(err)
649+
}
650+
return h.Sum256()
651+
}
652+
653+
b.ResetTimer()
654+
for i := 0; i < b.N; i++ {
655+
h.Reset()
656+
_ = txHash(h, &genesisCoinbaseTx)
657+
}
658+
}
659+
637660
// BenchmarkHashB performs a benchmark on how long it takes to perform a hash
638661
// returning a byte slice.
639662
func BenchmarkHashB(b *testing.B) {

wire/blockheader.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/decred/dcrd/chaincfg/chainhash"
14+
"github.com/decred/dcrd/crypto/blake256"
1415
"lukechampine.com/blake3"
1516
)
1617

@@ -88,36 +89,37 @@ type BlockHeader struct {
8889
// header.
8990
const blockHeaderLen = 180
9091

91-
// BlockHash computes the block identifier hash for the given block header.
92+
// BlockHash computes the BLAKE-256 block identifier hash for the given block
93+
// header.
9294
func (h *BlockHeader) BlockHash() chainhash.Hash {
9395
// Encode the header and hash everything prior to the number of
9496
// transactions. Ignore the error returns since there is no way the encode
9597
// could fail except being out of memory which would cause a run-time panic.
96-
buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload))
97-
_ = writeBlockHeader(buf, 0, h)
98-
99-
return chainhash.HashH(buf.Bytes())
98+
hasher := blake256.NewHasher256()
99+
_ = writeBlockHeader(hasher, 0, h)
100+
return hasher.Sum256()
100101
}
101102

102-
// PowHashV1 calculates and returns the version 1 proof of work hash for the
103-
// block header.
103+
// PowHashV1 calculates and returns the version 1 proof of work BLAKE-256 hash
104+
// for the block header.
104105
//
105106
// NOTE: This is the original proof of work hash function used at Decred launch
106107
// and applies to all blocks prior to the activation of DCP0011.
107108
func (h *BlockHeader) PowHashV1() chainhash.Hash {
108109
return h.BlockHash()
109110
}
110111

111-
// PowHashV2 calculates and returns the version 2 proof of work hash as defined
112-
// in DCP0011 for the block header.
112+
// PowHashV2 calculates and returns the version 2 proof of work BLAKE3 hash as
113+
// defined in DCP0011 for the block header.
113114
func (h *BlockHeader) PowHashV2() chainhash.Hash {
114115
// Encode the header and hash everything prior to the number of
115116
// transactions. Ignore the error returns since there is no way the encode
116117
// could fail except being out of memory which would cause a run-time panic.
117-
buf := bytes.NewBuffer(make([]byte, 0, MaxBlockHeaderPayload))
118-
_ = writeBlockHeader(buf, 0, h)
119-
120-
return blake3.Sum256(buf.Bytes())
118+
var digest chainhash.Hash
119+
hasher := blake3.New(len(digest), nil)
120+
_ = writeBlockHeader(hasher, 0, h)
121+
hasher.Sum(digest[:0])
122+
return digest
121123
}
122124

123125
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.

wire/common.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"time"
1616

1717
"github.com/decred/dcrd/chaincfg/chainhash"
18+
"github.com/decred/dcrd/crypto/blake256"
19+
"lukechampine.com/blake3"
1820
)
1921

2022
const (
@@ -382,6 +384,18 @@ func shortWrite(w io.Writer, cb func() (data [8]byte, size int)) error {
382384
w.Write(data[:size])
383385
return nil
384386

387+
// Hashing transactions can be optimized by writing directly to the
388+
// BLAKE-256 hasher.
389+
case *blake256.Hasher256:
390+
w.Write(data[:size])
391+
return nil
392+
393+
// Hashing block headers can be optimized by writing directly to the
394+
// BLAKE-3 hasher.
395+
case *blake3.Hasher:
396+
w.Write(data[:size])
397+
return nil
398+
385399
default:
386400
p := binarySerializer.Borrow()[:size]
387401
copy(p, data[:size])
@@ -813,6 +827,8 @@ func WriteVarString(w io.Writer, pver uint32, str string) error {
813827
switch w := w.(type) {
814828
case *bytes.Buffer:
815829
_, err = w.WriteString(str)
830+
case *blake256.Hasher256:
831+
w.WriteString(str)
816832
default:
817833
_, err = w.Write([]byte(str))
818834
}

wire/go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ go 1.17
55
require (
66
github.com/davecgh/go-spew v1.1.1
77
github.com/decred/dcrd/chaincfg/chainhash v1.0.5
8+
github.com/decred/dcrd/crypto/blake256 v1.1.0
89
lukechampine.com/blake3 v1.3.0
910
)
1011

11-
require (
12-
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
13-
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
14-
)
12+
require github.com/klauspost/cpuid/v2 v2.0.9 // indirect

wire/msgtx.go

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strconv"
1313

1414
"github.com/decred/dcrd/chaincfg/chainhash"
15+
"github.com/decred/dcrd/crypto/blake256"
1516
)
1617

1718
const (
@@ -391,24 +392,28 @@ func (msg *MsgTx) serialize(serType TxSerializeType) ([]byte, error) {
391392
return buf.Bytes(), nil
392393
}
393394

394-
// mustSerialize returns the serialization of the transaction for the provided
395-
// serialization type without modifying the original transaction. It will panic
396-
// if any errors occur.
397-
func (msg *MsgTx) mustSerialize(serType TxSerializeType) []byte {
398-
serialized, err := msg.serialize(serType)
395+
// mustHash returns the hash of the transaction for the provided
396+
// serialization type without modifying the original transaction.
397+
// It will panic if serialization fails.
398+
func (msg *MsgTx) mustHash(hasher *blake256.Hasher256, serType TxSerializeType) chainhash.Hash {
399+
// Shallow copy so the serialization type can be changed without
400+
// modifying the original transaction.
401+
mtxCopy := *msg
402+
mtxCopy.SerType = serType
403+
err := mtxCopy.Serialize(hasher)
399404
if err != nil {
400405
panic(fmt.Sprintf("MsgTx failed serializing for type %v",
401406
serType))
402407
}
403-
return serialized
408+
return hasher.Sum256()
404409
}
405410

406-
// TxHash generates the hash for the transaction prefix. Since it does not
407-
// contain any witness data, it is not malleable and therefore is stable for
408-
// use in unconfirmed transaction chains.
411+
// TxHash generates the BLAKE-256 hash for the transaction prefix. Since it
412+
// does not contain any witness data, it is not malleable and therefore is
413+
// stable for use in unconfirmed transaction chains.
409414
func (msg *MsgTx) TxHash() chainhash.Hash {
410415
// TxHash should always calculate a non-witnessed hash.
411-
return chainhash.HashH(msg.mustSerialize(TxSerializeNoWitness))
416+
return msg.mustHash(blake256.NewHasher256(), TxSerializeNoWitness)
412417
}
413418

414419
// CachedTxHash is equivalent to calling TxHash, however it caches the result so
@@ -433,29 +438,29 @@ func (msg *MsgTx) RecacheTxHash() *chainhash.Hash {
433438
return msg.CachedHash
434439
}
435440

436-
// TxHashWitness generates the hash for the transaction witness.
441+
// TxHashWitness generates the BLAKE-256 hash for the transaction witness.
437442
func (msg *MsgTx) TxHashWitness() chainhash.Hash {
438443
// TxHashWitness should always calculate a witnessed hash.
439-
return chainhash.HashH(msg.mustSerialize(TxSerializeOnlyWitness))
444+
return msg.mustHash(blake256.NewHasher256(), TxSerializeOnlyWitness)
440445
}
441446

442-
// TxHashFull generates the hash for the transaction prefix || witness. It first
443-
// obtains the hashes for both the transaction prefix and witness, then
444-
// concatenates them and hashes the result.
447+
// TxHashFull generates the hash for the transaction prefix || witness. This
448+
// is the BLAKE-256 hash of the concatenation of the individual prefix and
449+
// witness hashes (and not the hash of the full serialization).
445450
func (msg *MsgTx) TxHashFull() chainhash.Hash {
446-
// Note that the inputs to the hashes, the serialized prefix and
447-
// witness, have different serialized versions because the serialized
448-
// encoding of the version includes the real transaction version in the
449-
// lower 16 bits and the transaction serialization type in the upper 16
450-
// bits. The real transaction version (lower 16 bits) will be the same
451-
// in both serializations.
452-
concat := make([]byte, chainhash.HashSize*2)
453-
prefixHash := msg.TxHash()
454-
witnessHash := msg.TxHashWitness()
455-
copy(concat[0:], prefixHash[:])
456-
copy(concat[chainhash.HashSize:], witnessHash[:])
457-
458-
return chainhash.HashH(concat)
451+
// Even for a transaction that has neither prefix nor witness (and
452+
// would otherwise hash to the same result), the prefix and witness
453+
// hashes will still differ due to the serialization type being
454+
// encoded into the upper 16 bits of the transaction version.
455+
hasher := blake256.NewHasher256()
456+
prefixHash := msg.mustHash(hasher, TxSerializeNoWitness)
457+
hasher.Reset()
458+
witnessHash := msg.mustHash(hasher, TxSerializeOnlyWitness)
459+
hasher.Reset()
460+
461+
hasher.WriteBytes(prefixHash[:])
462+
hasher.WriteBytes(witnessHash[:])
463+
return hasher.Sum256()
459464
}
460465

461466
// Copy creates a deep copy of a transaction so that the original does not get

0 commit comments

Comments
 (0)