Created
April 13, 2023 00:55
-
-
Save losh11/7dbe0b5e252edb6abaf4844243db5b31 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// LOSHY | |
const TxFlagMarker = 0x00 | |
type TxFlag = byte | |
const ( | |
WitnessFlag TxFlag = 0x01 | |
MwebFlag TxFlag = 0x08 | |
) | |
type scriptFreeList chan []byte | |
const freeListMaxItems = 12500 | |
const freeListMaxScriptSize = 512 | |
var scriptPool scriptFreeList = make(chan []byte, freeListMaxItems) | |
func (c scriptFreeList) Return(buf []byte) { | |
// Ignore any buffers returned that aren't the expected size for the | |
// free list. | |
if cap(buf) != freeListMaxScriptSize { | |
return | |
} | |
// Return the buffer to the free list when it's not full. Otherwise let | |
// it be garbage collected. | |
select { | |
case c <- buf: | |
default: | |
// Let it go to the garbage collector. | |
} | |
} | |
type MsgTx struct { | |
Version int32 | |
TxIn []*wire.TxIn | |
TxOut []*wire.TxOut | |
LockTime uint32 | |
IsHogEx bool | |
Kern0 []byte | |
} | |
func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc wire.MessageEncoding) error { | |
dec := newDecoder(r) | |
version, err := dec.readUint32() | |
if err != nil { | |
return err | |
} | |
msg.Version = int32(version) | |
count, err := dec.readCompactSize() | |
if err != nil { | |
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. | |
hasMweb := false | |
hasWitness := false | |
if count == TxFlagMarker && enc == wire.WitnessEncoding { | |
var flag [1]TxFlag | |
// The count varint was in fact the flag marker byte. Next, we need to | |
// read the flag value, which is a single byte. | |
if _, err = io.ReadFull(r, flag[:]); err != nil { | |
return err | |
} | |
hasWitness = (flag[0]&WitnessFlag != 0) | |
flag[0] = flag[0] &^ WitnessFlag | |
hasMweb = (flag[0]&MwebFlag != 0) | |
flag[0] = flag[0] &^ MwebFlag | |
if flag[0] != 0 { | |
// str := fmt.Sprintf("Unknown flag %x, known flags:[%d, %d]", flag, WitnessFlag, MwebFlag) | |
return nil // LOSHY: messageError("MsgTx.BtcDecode", str) | |
} | |
// With the Segregated Witness specific fields decoded, we can | |
// now read in the actual txin count. | |
count, err = dec.readCompactSize() | |
if err != nil { | |
return err | |
} | |
} | |
// Prevent more input transactions than could possibly fit into a | |
// message. It would be possible to cause memory exhaustion and panics | |
// without a sane upper bound on this count. | |
if count > uint64(maxTxInPerMessage) { | |
// str := fmt.Sprintf("too many input transactions to fit into "+ | |
// "max message size [count %d, max %d]", count, | |
// maxTxInPerMessage) | |
return nil // messageError("MsgTx.BtcDecode", str) | |
} | |
// returnScriptBuffers is a closure that returns any script buffers that | |
// were borrowed from the pool when there are any deserialization | |
// errors. This is only valid to call before the final step which | |
// replaces the scripts with the location in a contiguous buffer and | |
// returns them. | |
returnScriptBuffers := func() { | |
for _, txIn := range msg.TxIn { | |
if txIn == nil { | |
continue | |
} | |
if txIn.SignatureScript != nil { | |
scriptPool.Return(txIn.SignatureScript) | |
} | |
for _, witnessElem := range txIn.Witness { | |
if witnessElem != nil { | |
scriptPool.Return(witnessElem) | |
} | |
} | |
} | |
for _, txOut := range msg.TxOut { | |
if txOut == nil || txOut.PkScript == nil { | |
continue | |
} | |
scriptPool.Return(txOut.PkScript) | |
} | |
} | |
// Deserialize the inputs. | |
var totalScriptSize uint64 | |
txIns := make([]wire.TxIn, count) | |
msg.TxIn = make([]*wire.TxIn, count) | |
for i := uint64(0); i < count; i++ { | |
// The pointer is set now in case a script buffer is borrowed | |
// and needs to be returned to the pool on error. | |
ti := &txIns[i] | |
msg.TxIn[i] = ti | |
err = dec.readTxIn(ti) | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
totalScriptSize += uint64(len(ti.SignatureScript)) | |
} | |
count, err = dec.readCompactSize() | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
// Prevent more output transactions than could possibly fit into a | |
// message. It would be possible to cause memory exhaustion and panics | |
// without a sane upper bound on this count. | |
if count > uint64(maxTxOutPerMessage) { | |
returnScriptBuffers() | |
// str := fmt.Sprintf("too many output transactions to fit into "+ | |
// "max message size [count %d, max %d]", count, | |
// maxTxOutPerMessage) | |
return nil // LOSHY: messageError("MsgTx.BtcDecode", str) | |
} | |
// Deserialize the outputs. | |
txOuts := make([]wire.TxOut, count) | |
msg.TxOut = make([]*wire.TxOut, count) | |
for i := uint64(0); i < count; i++ { | |
// The pointer is set now in case a script buffer is borrowed | |
// and needs to be returned to the pool on error. | |
to := &txOuts[i] | |
msg.TxOut[i] = to | |
err = dec.readTxOut(to) | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
totalScriptSize += uint64(len(to.PkScript)) | |
} | |
// If the transaction's flag byte isn't 0x00 at this point, then one or | |
// more of its inputs has accompanying witness data. | |
if hasWitness { | |
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 | |
// varint which encodes the number of stack items. | |
witCount, err := dec.readCompactSize() | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
// Prevent a possible memory exhaustion attack by | |
// limiting the witCount value to a sane upper bound. | |
if witCount > maxWitnessItemsPerInput { | |
returnScriptBuffers() | |
// str := fmt.Sprintf("too many witness items to fit "+ | |
// "into max message size [count %d, max %d]", | |
// witCount, maxWitnessItemsPerInput) | |
return nil //LOSHY: messageError("MsgTx.BtcDecode", str) | |
} | |
// Then for witCount number of stack items, each item | |
// has a varint length prefix, followed by the witness | |
// item itself. | |
txin.Witness = make([][]byte, witCount) | |
for j := uint64(0); j < witCount; j++ { | |
txin.Witness[j], err = wire.ReadVarBytes( | |
r, pver, maxWitnessItemSize, "script witness item", | |
) | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
totalScriptSize += uint64(len(txin.Witness[j])) | |
} | |
} | |
} | |
var kern0 []byte | |
var isHogEx bool | |
if hasMweb { | |
kern0, isHogEx, err = dec.readMWTX() | |
msg.IsHogEx = isHogEx | |
msg.Kern0 = kern0 | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
if isHogEx && len(msg.TxOut) == 0 { | |
return nil // LOSHY: originally errors out | |
} | |
msg.LockTime, err = dec.readUint32() | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
} else { | |
msg.LockTime, err = dec.readUint32() | |
if err != nil { | |
returnScriptBuffers() | |
return err | |
} | |
} | |
// Create a single allocation to house all of the scripts and set each | |
// input signature script and output public key script to the | |
// appropriate subslice of the overall contiguous buffer. Then, return | |
// each individual script buffer back to the pool so they can be reused | |
// for future deserializations. This is done because it significantly | |
// reduces the number of allocations the garbage collector needs to | |
// track, which in turn improves performance and drastically reduces the | |
// amount of runtime overhead that would otherwise be needed to keep | |
// track of millions of small allocations. | |
// | |
// NOTE: It is no longer valid to call the returnScriptBuffers closure | |
// after these blocks of code run because it is already done and the | |
// scripts in the transaction inputs and outputs no longer point to the | |
// buffers. | |
var offset uint64 | |
scripts := make([]byte, totalScriptSize) | |
for i := 0; i < len(msg.TxIn); i++ { | |
// Copy the signature script into the contiguous buffer at the | |
// appropriate offset. | |
signatureScript := msg.TxIn[i].SignatureScript | |
copy(scripts[offset:], signatureScript) | |
// Reset the signature script of the transaction input to the | |
// slice of the contiguous buffer where the script lives. | |
scriptSize := uint64(len(signatureScript)) | |
end := offset + scriptSize | |
msg.TxIn[i].SignatureScript = scripts[offset:end:end] | |
offset += scriptSize | |
// Return the temporary script buffer to the pool. | |
scriptPool.Return(signatureScript) | |
for j := 0; j < len(msg.TxIn[i].Witness); j++ { | |
// Copy each item within the witness stack for this | |
// input into the contiguous buffer at the appropriate | |
// offset. | |
witnessElem := msg.TxIn[i].Witness[j] | |
copy(scripts[offset:], witnessElem) | |
// Reset the witness item within the stack to the slice | |
// of the contiguous buffer where the witness lives. | |
witnessElemSize := uint64(len(witnessElem)) | |
end := offset + witnessElemSize | |
msg.TxIn[i].Witness[j] = scripts[offset:end:end] | |
offset += witnessElemSize | |
// Return the temporary buffer used for the witness stack | |
// item to the pool. | |
scriptPool.Return(witnessElem) | |
} | |
} | |
for i := 0; i < len(msg.TxOut); i++ { | |
// Copy the public key script into the contiguous buffer at the | |
// appropriate offset. | |
pkScript := msg.TxOut[i].PkScript | |
copy(scripts[offset:], pkScript) | |
// Reset the public key script of the transaction output to the | |
// slice of the contiguous buffer where the script lives. | |
scriptSize := uint64(len(pkScript)) | |
end := offset + scriptSize | |
msg.TxOut[i].PkScript = scripts[offset:end:end] | |
offset += scriptSize | |
// Return the temporary script buffer to the pool. | |
scriptPool.Return(pkScript) | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment