Skip to content

Commit

Permalink
btcutil: reuse serialized tx during TxHash
Browse files Browse the repository at this point in the history
btcutil.Block caches the serialized raw bytes of the block during ibd.
This serialized block bytes includes the serialized tx. The current tx
hash generation will re-serialized the de-serialized tx to create the
raw bytes and it'll only then hash that.

This commit changes the code so that the re-serialization never happens,
saving tons of cpu and memory overhead.
  • Loading branch information
kcalvinalvin committed Aug 21, 2023
1 parent 40d7a0a commit 3afc77e
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 4 deletions.
14 changes: 14 additions & 0 deletions btcutil/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,26 @@ func (b *Block) Transactions() []*Tx {
b.transactions = make([]*Tx, len(b.msgBlock.Transactions))
}

// Offset of each tx. 80 accounts for the block header size.
offset := 80 + wire.VarIntSerializeSize(uint64(len(b.msgBlock.Transactions)))

// Generate and cache the wrapped transactions for all that haven't
// already been done.
for i, tx := range b.transactions {
if tx == nil {
newTx := NewTx(b.msgBlock.Transactions[i])
newTx.SetIndex(i)

size := b.msgBlock.Transactions[i].SerializeSize()

// The block may not always have the serializedBlock.
if len(b.serializedBlock) > 0 {
// This allows for the reuse of the already serialized tx.
newTx.SetBytes(b.serializedBlock[offset : offset+size])

// Increment offset for this block.
offset += size
}
b.transactions[i] = newTx
}
}
Expand Down
58 changes: 54 additions & 4 deletions btcutil/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Tx struct {
txHashWitness *chainhash.Hash // Cached transaction witness hash
txHasWitness *bool // If the transaction has witness data
txIndex int // Position within a block or TxIndexUnknown
rawBytes []byte // Raw bytes for the tx in the raw block.
}

// MsgTx returns the underlying wire.MsgTx for the transaction.
Expand All @@ -37,30 +38,69 @@ func (t *Tx) MsgTx() *wire.MsgTx {

// Hash returns the hash of the transaction. This is equivalent to
// calling TxHash on the underlying wire.MsgTx, however it caches the
// result so subsequent calls are more efficient.
// result so subsequent calls are more efficient. If the Tx has the
// raw bytes of the tx cached, it will use that and skip serialization.
func (t *Tx) Hash() *chainhash.Hash {
// Return the cached hash if it has already been generated.
if t.txHash != nil {
return t.txHash
}

// Cache the hash and return it.
hash := t.msgTx.TxHash()
var hash chainhash.Hash

// If we have the raw bytes, then don't call msgTx.TxHash as that has the
// overhead of serialization.
if len(t.rawBytes) > 0 {
// If the raw bytes contain the witness, we must strip it out before
// calculating the hash.
if t.HasWitness() {
baseSize := t.msgTx.SerializeSizeStripped()
bytes := make([]byte, 0, baseSize)

// Append the version bytes.
offset := 4
bytes = append(bytes, t.rawBytes[:offset]...)

// Append the input and output bytes. -8 to account for the
// version bytes and the locktime bytes.
//
// Skip the 2 bytes for the witness encoding.
offset += 2
bytes = append(bytes, t.rawBytes[offset:offset+baseSize-8]...)

// Append the last 4 bytes which are the locktime bytes.
bytes = append(bytes, t.rawBytes[len(t.rawBytes)-4:]...)

hash = chainhash.DoubleHashH(bytes)
} else {
hash = chainhash.DoubleHashH(t.rawBytes)
}
} else {
hash = t.msgTx.TxHash()
}
t.txHash = &hash
return &hash
}

// WitnessHash returns the witness hash (wtxid) of the transaction. This is
// equivalent to calling WitnessHash on the underlying wire.MsgTx, however it
// caches the result so subsequent calls are more efficient.
// caches the result so subsequent calls are more efficient. If the Tx has the
// raw bytes of the tx cached, it will use that and skip serialization.
func (t *Tx) WitnessHash() *chainhash.Hash {
// Return the cached hash if it has already been generated.
if t.txHashWitness != nil {
return t.txHashWitness
}

// Cache the hash and return it.
hash := t.msgTx.WitnessHash()
var hash chainhash.Hash
if len(t.rawBytes) > 0 {
hash = chainhash.DoubleHashH(t.rawBytes)
} else {
hash = t.msgTx.WitnessHash()
}

t.txHashWitness = &hash
return &hash
}
Expand Down Expand Up @@ -90,6 +130,16 @@ func (t *Tx) SetIndex(index int) {
t.txIndex = index
}

// SetBytes sets the raw bytes of the tx.
func (t *Tx) SetBytes(bytes []byte) {
t.rawBytes = bytes
}

// RawBytes returns the cached raw bytes of the tx.
func (t *Tx) RawBytes() []byte {
return t.rawBytes
}

// NewTx returns a new instance of a bitcoin transaction given an underlying
// wire.MsgTx. See Tx.
func NewTx(msgTx *wire.MsgTx) *Tx {
Expand Down

0 comments on commit 3afc77e

Please sign in to comment.