diff --git a/wire/common.go b/wire/common.go index c908fa3a..033d3dad 100644 --- a/wire/common.go +++ b/wire/common.go @@ -14,8 +14,161 @@ import ( "github.com/btcsuite/fastsha256" ) -// MaxVarIntPayload is the maximum payload size for a variable length integer. -const MaxVarIntPayload = 9 +const ( + // MaxVarIntPayload is the maximum payload size for a variable length integer. + MaxVarIntPayload = 9 + + // binaryFreeListMaxItems is the number of buffers to keep in the free + // list to use for binary serialization and deserialization. + binaryFreeListMaxItems = 1024 +) + +var ( + // littleEndian is a convenience variable since binary.LittleEndian is + // quite long. + littleEndian = binary.LittleEndian + + // bigEndian is a convenience variable since binary.BigEndian is quite + // long. + bigEndian = binary.BigEndian +) + +// binaryFreeList defines a concurrent safe free list of byte slices (up to the +// maximum number defined by the binaryFreeListMaxItems constant) that have a +// cap of 8 (thus it supports up to a uint64). It is used to provide temporary +// buffers for serializing and deserializing primitive numbers to and from their +// binary encoding in order to greatly reduce the number of allocations +// required. +// +// For convenience, functions are provided for each of the primitive unsigned +// integers that automatically obtain a buffer from the free list, perform the +// necessary binary conversion, read from or write to the given io.Reader or +// io.Writer, and return the buffer to the free list. +type binaryFreeList chan []byte + +// Borrow returns a byte slice from the free list with a length of 8. A new +// buffer is allocated if there are not any available on the free list. +func (l binaryFreeList) Borrow() []byte { + var buf []byte + select { + case buf = <-l: + default: + buf = make([]byte, 8) + } + return buf[:8] +} + +// Return puts the provided byte slice back on the free list. The buffer MUST +// have been obtained via the Borrow function and therefore have a cap of 8. +func (l binaryFreeList) Return(buf []byte) { + select { + case l <- buf: + default: + // Let it go to the garbage collector. + } +} + +// Uint8 reads a single byte from the provided reader using a buffer from the +// free list and returns it as a uint8. +func (l binaryFreeList) Uint8(r io.Reader) (uint8, error) { + buf := l.Borrow()[:1] + if _, err := io.ReadFull(r, buf); err != nil { + l.Return(buf) + return 0, err + } + rv := buf[0] + l.Return(buf) + return rv, nil +} + +// Uint16 reads two bytes from the provided reader using a buffer from the +// free list, converts it to a number using the provided byte order, and returns +// the resulting uint16. +func (l binaryFreeList) Uint16(r io.Reader, byteOrder binary.ByteOrder) (uint16, error) { + buf := l.Borrow()[:2] + if _, err := io.ReadFull(r, buf); err != nil { + l.Return(buf) + return 0, err + } + rv := byteOrder.Uint16(buf) + l.Return(buf) + return rv, nil +} + +// Uint32 reads four bytes from the provided reader using a buffer from the +// free list, converts it to a number using the provided byte order, and returns +// the resulting uint32. +func (l binaryFreeList) Uint32(r io.Reader, byteOrder binary.ByteOrder) (uint32, error) { + buf := l.Borrow()[:4] + if _, err := io.ReadFull(r, buf); err != nil { + l.Return(buf) + return 0, err + } + rv := byteOrder.Uint32(buf) + l.Return(buf) + return rv, nil +} + +// Uint64 reads eight bytes from the provided reader using a buffer from the +// free list, converts it to a number using the provided byte order, and returns +// the resulting uint64. +func (l binaryFreeList) Uint64(r io.Reader, byteOrder binary.ByteOrder) (uint64, error) { + buf := l.Borrow()[:8] + if _, err := io.ReadFull(r, buf); err != nil { + l.Return(buf) + return 0, err + } + rv := byteOrder.Uint64(buf) + l.Return(buf) + return rv, nil +} + +// PutUint8 copies the provided uint8 into a buffer from the free list and +// writes the resulting byte to the given writer. +func (l binaryFreeList) PutUint8(w io.Writer, val uint8) error { + buf := l.Borrow()[:1] + buf[0] = val + _, err := w.Write(buf) + l.Return(buf) + return err +} + +// PutUint16 serializes the provided uint16 using the given byte order into a +// buffer from the free list and writes the resulting two bytes to the given +// writer. +func (l binaryFreeList) PutUint16(w io.Writer, byteOrder binary.ByteOrder, val uint16) error { + buf := l.Borrow()[:2] + byteOrder.PutUint16(buf, val) + _, err := w.Write(buf) + l.Return(buf) + return err +} + +// PutUint32 serializes the provided uint32 using the given byte order into a +// buffer from the free list and writes the resulting four bytes to the given +// writer. +func (l binaryFreeList) PutUint32(w io.Writer, byteOrder binary.ByteOrder, val uint32) error { + buf := l.Borrow()[:4] + byteOrder.PutUint32(buf, val) + _, err := w.Write(buf) + l.Return(buf) + return err +} + +// PutUint64 serializes the provided uint64 using the given byte order into a +// buffer from the free list and writes the resulting eight bytes to the given +// writer. +func (l binaryFreeList) PutUint64(w io.Writer, byteOrder binary.ByteOrder, val uint64) error { + buf := l.Borrow()[:8] + byteOrder.PutUint64(buf, val) + _, err := w.Write(buf) + l.Return(buf) + return err +} + +// binarySerializer provides a free list of buffers to use for serializing and +// deserializing primitive integer values to and from io.Readers and io.Writers. +var binarySerializer binaryFreeList = make(chan []byte, binaryFreeListMaxItems) // errNonCanonicalVarInt is the common format string used for non-canonically // encoded variable length integer errors. @@ -25,54 +178,47 @@ var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " + // readElement reads the next sequence of bytes from r using little endian // depending on the concrete type of element pointed to. func readElement(r io.Reader, element interface{}) error { - var scratch [8]byte - // Attempt to read the element based on the concrete type via fast // type assertions first. switch e := element.(type) { case *int32: - b := scratch[0:4] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - *e = int32(binary.LittleEndian.Uint32(b)) + *e = int32(rv) return nil case *uint32: - b := scratch[0:4] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - *e = binary.LittleEndian.Uint32(b) + *e = rv return nil case *int64: - b := scratch[0:8] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return err } - *e = int64(binary.LittleEndian.Uint64(b)) + *e = int64(rv) return nil case *uint64: - b := scratch[0:8] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return err } - *e = binary.LittleEndian.Uint64(b) + *e = rv return nil case *bool: - b := scratch[0:1] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint8(r) if err != nil { return err } - if b[0] == 0x00 { + if rv == 0x00 { *e = false } else { *e = true @@ -111,54 +257,49 @@ func readElement(r io.Reader, element interface{}) error { return nil case *ServiceFlag: - b := scratch[0:8] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return err } - *e = ServiceFlag(binary.LittleEndian.Uint64(b)) + *e = ServiceFlag(rv) return nil case *InvType: - b := scratch[0:4] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - *e = InvType(binary.LittleEndian.Uint32(b)) + *e = InvType(rv) return nil case *BitcoinNet: - b := scratch[0:4] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - *e = BitcoinNet(binary.LittleEndian.Uint32(b)) + *e = BitcoinNet(rv) return nil case *BloomUpdateType: - b := scratch[0:1] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint8(r) if err != nil { return err } - *e = BloomUpdateType(b[0]) + *e = BloomUpdateType(rv) return nil case *RejectCode: - b := scratch[0:1] - _, err := io.ReadFull(r, b) + rv, err := binarySerializer.Uint8(r) if err != nil { return err } - *e = RejectCode(b[0]) + *e = RejectCode(rv) return nil } // Fall back to the slower binary.Read if a fast path was not available // above. - return binary.Read(r, binary.LittleEndian, element) + return binary.Read(r, littleEndian, element) } // readElements reads multiple items from r. It is equivalent to multiple @@ -175,55 +316,44 @@ func readElements(r io.Reader, elements ...interface{}) error { // writeElement writes the little endian representation of element to w. func writeElement(w io.Writer, element interface{}) error { - var scratch [8]byte - // Attempt to write the element based on the concrete type via fast // type assertions first. switch e := element.(type) { case int32: - b := scratch[0:4] - binary.LittleEndian.PutUint32(b, uint32(e)) - _, err := w.Write(b) + err := binarySerializer.PutUint32(w, littleEndian, uint32(e)) if err != nil { return err } return nil case uint32: - b := scratch[0:4] - binary.LittleEndian.PutUint32(b, e) - _, err := w.Write(b) + err := binarySerializer.PutUint32(w, littleEndian, e) if err != nil { return err } return nil case int64: - b := scratch[0:8] - binary.LittleEndian.PutUint64(b, uint64(e)) - _, err := w.Write(b) + err := binarySerializer.PutUint64(w, littleEndian, uint64(e)) if err != nil { return err } return nil case uint64: - b := scratch[0:8] - binary.LittleEndian.PutUint64(b, e) - _, err := w.Write(b) + err := binarySerializer.PutUint64(w, littleEndian, e) if err != nil { return err } return nil case bool: - b := scratch[0:1] + var err error if e == true { - b[0] = 0x01 + err = binarySerializer.PutUint8(w, 0x01) } else { - b[0] = 0x00 + err = binarySerializer.PutUint8(w, 0x00) } - _, err := w.Write(b) if err != nil { return err } @@ -261,45 +391,35 @@ func writeElement(w io.Writer, element interface{}) error { return nil case ServiceFlag: - b := scratch[0:8] - binary.LittleEndian.PutUint64(b, uint64(e)) - _, err := w.Write(b) + err := binarySerializer.PutUint64(w, littleEndian, uint64(e)) if err != nil { return err } return nil case InvType: - b := scratch[0:4] - binary.LittleEndian.PutUint32(b, uint32(e)) - _, err := w.Write(b) + err := binarySerializer.PutUint32(w, littleEndian, uint32(e)) if err != nil { return err } return nil case BitcoinNet: - b := scratch[0:4] - binary.LittleEndian.PutUint32(b, uint32(e)) - _, err := w.Write(b) + err := binarySerializer.PutUint32(w, littleEndian, uint32(e)) if err != nil { return err } return nil case BloomUpdateType: - b := scratch[0:1] - b[0] = uint8(e) - _, err := w.Write(b) + err := binarySerializer.PutUint8(w, uint8(e)) if err != nil { return err } return nil case RejectCode: - b := scratch[0:1] - b[0] = uint8(e) - _, err := w.Write(b) + err := binarySerializer.PutUint8(w, uint8(e)) if err != nil { return err } @@ -308,7 +428,7 @@ func writeElement(w io.Writer, element interface{}) error { // Fall back to the slower binary.Write if a fast path was not available // above. - return binary.Write(w, binary.LittleEndian, element) + return binary.Write(w, littleEndian, element) } // writeElements writes multiple items to w. It is equivalent to multiple @@ -325,21 +445,19 @@ func writeElements(w io.Writer, elements ...interface{}) error { // ReadVarInt reads a variable length integer from r and returns it as a uint64. func ReadVarInt(r io.Reader, pver uint32) (uint64, error) { - var b [8]byte - _, err := io.ReadFull(r, b[0:1]) + discriminant, err := binarySerializer.Uint8(r) if err != nil { return 0, err } var rv uint64 - discriminant := uint8(b[0]) switch discriminant { case 0xff: - _, err := io.ReadFull(r, b[:]) + sv, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return 0, err } - rv = binary.LittleEndian.Uint64(b[:]) + rv = sv // The encoding is not canonical if the value could have been // encoded using fewer bytes. @@ -350,11 +468,11 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) { } case 0xfe: - _, err := io.ReadFull(r, b[0:4]) + sv, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return 0, err } - rv = uint64(binary.LittleEndian.Uint32(b[:])) + rv = uint64(sv) // The encoding is not canonical if the value could have been // encoded using fewer bytes. @@ -365,11 +483,11 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) { } case 0xfd: - _, err := io.ReadFull(r, b[0:2]) + sv, err := binarySerializer.Uint16(r, littleEndian) if err != nil { return 0, err } - rv = uint64(binary.LittleEndian.Uint16(b[:])) + rv = uint64(sv) // The encoding is not canonical if the value could have been // encoded using fewer bytes. @@ -390,31 +508,30 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) { // on its value. func WriteVarInt(w io.Writer, pver uint32, val uint64) error { if val < 0xfd { - _, err := w.Write([]byte{uint8(val)}) - return err + return binarySerializer.PutUint8(w, uint8(val)) } if val <= math.MaxUint16 { - var buf [3]byte - buf[0] = 0xfd - binary.LittleEndian.PutUint16(buf[1:], uint16(val)) - _, err := w.Write(buf[:]) - return err + err := binarySerializer.PutUint8(w, 0xfd) + if err != nil { + return err + } + return binarySerializer.PutUint16(w, littleEndian, uint16(val)) } if val <= math.MaxUint32 { - var buf [5]byte - buf[0] = 0xfe - binary.LittleEndian.PutUint32(buf[1:], uint32(val)) - _, err := w.Write(buf[:]) - return err + err := binarySerializer.PutUint8(w, 0xfe) + if err != nil { + return err + } + return binarySerializer.PutUint32(w, littleEndian, uint32(val)) } - var buf [9]byte - buf[0] = 0xff - binary.LittleEndian.PutUint64(buf[1:], val) - _, err := w.Write(buf[:]) - return err + err := binarySerializer.PutUint8(w, 0xff) + if err != nil { + return err + } + return binarySerializer.PutUint64(w, littleEndian, val) } // VarIntSerializeSize returns the number of bytes it would take to serialize @@ -536,12 +653,11 @@ func WriteVarBytes(w io.Writer, pver uint32, bytes []byte) error { // unexported version takes a reader primarily to ensure the error paths // can be properly tested by passing a fake reader in the tests. func randomUint64(r io.Reader) (uint64, error) { - var b [8]byte - _, err := io.ReadFull(r, b[:]) + rv, err := binarySerializer.Uint64(r, bigEndian) if err != nil { return 0, err } - return binary.BigEndian.Uint64(b[:]), nil + return rv, nil } // RandomUint64 returns a cryptographically random uint64 value. diff --git a/wire/msgtx.go b/wire/msgtx.go index 4542dd29..43de4a74 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -6,7 +6,6 @@ package wire import ( "bytes" - "encoding/binary" "fmt" "io" "strconv" @@ -243,12 +242,11 @@ 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) error { - var buf [4]byte - _, err := io.ReadFull(r, buf[:]) + version, err := binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - msg.Version = int32(binary.LittleEndian.Uint32(buf[:])) + msg.Version = int32(version) count, err := ReadVarInt(r, pver) if err != nil { @@ -300,11 +298,10 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { msg.TxOut[i] = &to } - _, err = io.ReadFull(r, buf[:]) + msg.LockTime, err = binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - msg.LockTime = binary.LittleEndian.Uint32(buf[:]) return nil } @@ -331,9 +328,7 @@ func (msg *MsgTx) Deserialize(r io.Reader) error { // See Serialize for encoding transactions to be stored to disk, such as in a // database, as opposed to encoding transactions for the wire. func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error { - var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], uint32(msg.Version)) - _, err := w.Write(buf[:]) + err := binarySerializer.PutUint32(w, littleEndian, uint32(msg.Version)) if err != nil { return err } @@ -364,8 +359,7 @@ func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error { } } - binary.LittleEndian.PutUint32(buf[:], msg.LockTime) - _, err = w.Write(buf[:]) + err = binarySerializer.PutUint32(w, littleEndian, msg.LockTime) if err != nil { return err } @@ -479,12 +473,11 @@ func readOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error { return err } - var buf [4]byte - _, err = io.ReadFull(r, buf[:]) + op.Index, err = binarySerializer.Uint32(r, littleEndian) if err != nil { return err } - op.Index = binary.LittleEndian.Uint32(buf[:]) + return nil } @@ -496,9 +489,7 @@ func writeOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) error return err } - var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], op.Index) - _, err = w.Write(buf[:]) + err = binarySerializer.PutUint32(w, littleEndian, op.Index) if err != nil { return err } @@ -521,12 +512,10 @@ func readTxIn(r io.Reader, pver uint32, version int32, ti *TxIn) error { return err } - var buf [4]byte - _, err = io.ReadFull(r, buf[:]) + err = readElement(r, &ti.Sequence) if err != nil { return err } - ti.Sequence = binary.LittleEndian.Uint32(buf[:]) return nil } @@ -544,9 +533,7 @@ func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn) error { return err } - var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], ti.Sequence) - _, err = w.Write(buf[:]) + err = binarySerializer.PutUint32(w, littleEndian, ti.Sequence) if err != nil { return err } @@ -557,12 +544,10 @@ func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn) error { // readTxOut reads the next sequence of bytes from r as a transaction output // (TxOut). func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { - var buf [8]byte - _, err := io.ReadFull(r, buf[:]) + err := readElement(r, &to.Value) if err != nil { return err } - to.Value = int64(binary.LittleEndian.Uint64(buf[:])) to.PkScript, err = ReadVarBytes(r, pver, MaxMessagePayload, "transaction output public key script") @@ -576,9 +561,7 @@ func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { // writeTxOut encodes to into the bitcoin protocol encoding for a transaction // output (TxOut) to w. func writeTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error { - var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], uint64(to.Value)) - _, err := w.Write(buf[:]) + err := binarySerializer.PutUint64(w, littleEndian, uint64(to.Value)) if err != nil { return err } diff --git a/wire/netaddress.go b/wire/netaddress.go index cf5a7956..bd58a9c1 100644 --- a/wire/netaddress.go +++ b/wire/netaddress.go @@ -108,7 +108,6 @@ func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { var timestamp time.Time var services ServiceFlag var ip [16]byte - var port uint16 // NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will // stop working somewhere around 2106. Also timestamp wasn't added until @@ -127,7 +126,7 @@ func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { return err } // Sigh. Bitcoin protocol mixes little and big endian. - err = binary.Read(r, binary.BigEndian, &port) + port, err := binarySerializer.Uint16(r, bigEndian) if err != nil { return err } @@ -163,7 +162,7 @@ func writeNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error { } // Sigh. Bitcoin protocol mixes little and big endian. - err = binary.Write(w, binary.BigEndian, na.Port) + err = binary.Write(w, bigEndian, na.Port) if err != nil { return err }