-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wire: cache the non-witness serialization of MsgTx to memoize part of… #1376
Changes from all commits
0924825
e2a7cd1
d045a70
5ba479d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -343,6 +343,12 @@ type MsgTx struct { | |
TxIn []*TxIn | ||
TxOut []*TxOut | ||
LockTime uint32 | ||
|
||
// cachedSeralizedNoWitness is a cached version of the serialization of | ||
// this transaction without witness data. When we decode a transaction, | ||
// we'll write out the non-witness bytes to this so we can quickly | ||
// calculate the TxHash later if needed. | ||
cachedSeralizedNoWitness []byte | ||
} | ||
|
||
// AddTxIn adds a transaction input to the message. | ||
|
@@ -357,13 +363,26 @@ func (msg *MsgTx) AddTxOut(to *TxOut) { | |
|
||
// TxHash generates the Hash for the transaction. | ||
func (msg *MsgTx) TxHash() chainhash.Hash { | ||
// Encode the transaction and calculate double sha256 on the result. | ||
// Ignore the error returns since the only way the encode could fail | ||
// is being out of memory or due to nil pointers, both of which would | ||
// cause a run-time panic. | ||
buf := bytes.NewBuffer(make([]byte, 0, msg.SerializeSizeStripped())) | ||
_ = msg.SerializeNoWitness(buf) | ||
return chainhash.DoubleHashH(buf.Bytes()) | ||
if msg.cachedSeralizedNoWitness == nil { | ||
// Encode the transaction and calculate double sha256 on the | ||
// result. Ignore the error returns since the only way the | ||
// encode could fail is being out of memory or due to nil | ||
// pointers, both of which would cause a run-time panic. | ||
strippedSize := msg.SerializeSizeStripped() | ||
buf := bytes.NewBuffer(make([]byte, 0, strippedSize)) | ||
_ = msg.SerializeNoWitness(buf) | ||
|
||
msg.cachedSeralizedNoWitness = buf.Bytes() | ||
} | ||
|
||
return chainhash.DoubleHashH(msg.cachedSeralizedNoWitness) | ||
} | ||
|
||
// WipeCache removes the cached serialized bytes of the transaction. This is | ||
// useful to be able to get the correct txid after mutating a transaction's | ||
// state. | ||
func (msg *MsgTx) WipeCache() { | ||
msg.cachedSeralizedNoWitness = nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we also need to call this in the helper methods like I'm mostly worrying about uses of |
||
} | ||
|
||
// WitnessHash generates the hash of the transaction serialized according to | ||
|
@@ -461,7 +480,14 @@ func (msg *MsgTx) Copy() *MsgTx { | |
// See Deserialize for decoding transactions stored to disk, such as in a | ||
// database, as opposed to decoding transactions from the wire. | ||
func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { | ||
version, err := binarySerializer.Uint32(r, littleEndian) | ||
// We'll use a tee reader in order to incrementally cache the raw | ||
// non-witness serialization of this transaction. We'll then later | ||
// cache this value as it allow to compute the TxHash more quickly, as | ||
// we don't need to re-serialize the entire transaction. | ||
var rawTxBuf bytes.Buffer | ||
rawTxTeeReader := io.TeeReader(r, &rawTxBuf) | ||
|
||
version, err := binarySerializer.Uint32(rawTxTeeReader, littleEndian) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -472,12 +498,15 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
return err | ||
} | ||
|
||
// A count of zero (meaning no TxIn's to the uninitiated) means that the | ||
// value is a TxFlagMarker, and hence indicates the presence of a flag. | ||
var flag [1]TxFlag | ||
// A count of zero (meaning no TxIn's to the uninitiated) indicates | ||
// this is a transaction with witness data. Notice that we don't use | ||
// the rawTxTeeReader here, as these are segwit specific bytes. | ||
var ( | ||
flag [1]byte | ||
hasWitneess bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/hasWitneess/hasWitness |
||
) | ||
if count == TxFlagMarker && enc == WitnessEncoding { | ||
// The count varint was in fact the flag marker byte. Next, we need to | ||
// read the flag value, which is a single byte. | ||
// Next, we need to read the flag, which is a single byte. | ||
if _, err = io.ReadFull(r, flag[:]); err != nil { | ||
return err | ||
} | ||
|
@@ -495,6 +524,15 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
if err != nil { | ||
return err | ||
} | ||
|
||
hasWitneess = true | ||
} | ||
|
||
// Write out the actual number of inputs as this won't be the very byte | ||
// series after the versino of segwit transactions. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/versino/version |
||
if WriteVarInt(&rawTxBuf, pver, count); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to add |
||
str := fmt.Sprintf("unable to write txin count: %v", err) | ||
return messageError("MsgTx.BtcDecode", str) | ||
} | ||
|
||
// Prevent more input transactions than could possibly fit into a | ||
|
@@ -545,15 +583,15 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
// and needs to be returned to the pool on error. | ||
ti := &txIns[i] | ||
msg.TxIn[i] = ti | ||
err = readTxIn(r, pver, msg.Version, ti) | ||
err = readTxIn(rawTxTeeReader, pver, msg.Version, ti) | ||
if err != nil { | ||
returnScriptBuffers() | ||
return err | ||
} | ||
totalScriptSize += uint64(len(ti.SignatureScript)) | ||
} | ||
|
||
count, err = ReadVarInt(r, pver) | ||
count, err = ReadVarInt(rawTxTeeReader, pver) | ||
if err != nil { | ||
returnScriptBuffers() | ||
return err | ||
|
@@ -578,7 +616,7 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
// and needs to be returned to the pool on error. | ||
to := &txOuts[i] | ||
msg.TxOut[i] = to | ||
err = ReadTxOut(r, pver, msg.Version, to) | ||
err = ReadTxOut(rawTxTeeReader, pver, msg.Version, to) | ||
if err != nil { | ||
returnScriptBuffers() | ||
return err | ||
|
@@ -588,7 +626,7 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
|
||
// If the transaction's flag byte isn't 0x00 at this point, then one or | ||
// more of its inputs has accompanying witness data. | ||
if flag[0] != 0 && enc == WitnessEncoding { | ||
if hasWitneess && enc == WitnessEncoding { | ||
for _, txin := range msg.TxIn { | ||
// For each input, the witness is encoded as a stack | ||
// with one or more items. Therefore, we first read a | ||
|
@@ -626,7 +664,9 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
} | ||
} | ||
|
||
msg.LockTime, err = binarySerializer.Uint32(r, littleEndian) | ||
msg.LockTime, err = binarySerializer.Uint32( | ||
rawTxTeeReader, littleEndian, | ||
) | ||
if err != nil { | ||
returnScriptBuffers() | ||
return err | ||
|
@@ -700,6 +740,11 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error | |
scriptPool.Return(pkScript) | ||
} | ||
|
||
// Now that we've decoded the entire transaction without any issues, | ||
// we'll cache the non-witness serialization so we can more quickly | ||
// calculate the TxHash in the future. | ||
msg.cachedSeralizedNoWitness = rawTxBuf.Bytes() | ||
|
||
return nil | ||
} | ||
|
||
|
@@ -832,6 +877,10 @@ func (msg *MsgTx) Serialize(w io.Writer) error { | |
// Serialize, however even if the source transaction has inputs with witness | ||
// data, the old serialization format will still be used. | ||
func (msg *MsgTx) SerializeNoWitness(w io.Writer) error { | ||
if msg.cachedSeralizedNoWitness != nil { | ||
w.Write(msg.cachedSeralizedNoWitness) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing return? |
||
|
||
return msg.BtcEncode(w, 0, BaseEncoding) | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/cachedSeralizedNoWitness/cachedSerializedNoWitness/