Skip to content

Instantly share code, notes, and snippets.

@losh11
Created April 13, 2023 00:55
Show Gist options
  • Save losh11/7dbe0b5e252edb6abaf4844243db5b31 to your computer and use it in GitHub Desktop.
Save losh11/7dbe0b5e252edb6abaf4844243db5b31 to your computer and use it in GitHub Desktop.
// 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