Skip to content
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: only borrow/return binaryFreeList buffers at the message level #2073

Merged
merged 52 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b434080
wire/bench_test: report allocs in benchmarks
cfromknecht Jan 25, 2020
a9edc32
wire/bench: add witness block
cfromknecht Jan 25, 2020
a371aeb
wire/common: optimize Read/WriteVarInt
cfromknecht Jan 25, 2020
6275db9
wire: introduce Read/WriteVarIntBuf to reuse buffers between invocations
cfromknecht Jan 25, 2020
e58aadc
wire/msgtx: use Read/WriteVarIntBuf in tx serialization
cfromknecht Jan 25, 2020
e12d32d
wire/msgtx: reuse tx-level buffer for version and locktime
cfromknecht Jan 25, 2020
7951aa5
wire/common: add optimized Read/WriteVarBytesBuf
cfromknecht Jan 25, 2020
b171012
wire/msgtx: introduce optimized read/writeOutPointBuf
cfromknecht Jan 25, 2020
d43d9d5
wire/msgtx: introduce optimized writeTxInBuf
cfromknecht Jan 25, 2020
4829ff7
wire/msgtx: use writeTxInBuf in txn encoding
cfromknecht Jan 25, 2020
99f6488
wire/msgtx: introduce optimized readScriptBuf
cfromknecht Apr 30, 2019
6f4a7a1
wire/msgtx: introduce optimized readTxInBuf
cfromknecht Apr 30, 2019
607eea1
wire/msgtx: use readTxInBuf in txn serialization
cfromknecht Jan 25, 2020
7c8844f
wire/msgtx: introduce optimized WriteTxOutBuf
cfromknecht Jan 25, 2020
48d31e5
wire/msgtx: use WriteTxOutBuf in txn serialization
cfromknecht Jan 25, 2020
aebc743
wire/msgtx: introduce optimized readTxOutBuf
cfromknecht Jan 25, 2020
24d4217
wire/msgtx: use readTxOutBuf in txn serialization
cfromknecht Jan 25, 2020
3bfd0c6
wire/msgtx: introduce optimized writeTxWitnessBuf
cfromknecht Jan 25, 2020
3a91303
wire/msgtx: use writeTxWitnessBuf in txn serialization
cfromknecht Jan 25, 2020
0cf8c19
wire/msgtx: use readScriptBuf in txn serialization
cfromknecht Jan 25, 2020
aa769e3
wire/bench_test: introduce optimized readBlockHeaderBuf
cfromknecht Jan 25, 2020
3cee06e
wire/blockheader: introduce optimized writeBlockHeaderBuf
cfromknecht Jan 25, 2020
674c220
wire/invvect: add optimized readInvVectBuf and writeInvVectBuf
cfromknecht Jan 25, 2020
4ebc651
wire/msggetblocks: optimize by reusing small buffer
cfromknecht Jan 25, 2020
ee1f807
wire/msgblock: use only one small buffer per block encode/decode
cfromknecht Jan 25, 2020
d8e0817
wire/msgblock: optimize DeserializeTxLoc by reusing small buffers
cfromknecht Jan 25, 2020
c0d35e6
wire/msggetheaders: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
83675cb
wire/msgheaders: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
d042fe0
wire/msggetcfheaders: use single small buffer for encode/decode
cfromknecht Jan 25, 2020
1c525db
wire/msgcfheaders: optimize encode/decode by using one small buffer
cfromknecht Jan 25, 2020
1990555
wire/msggetcfcheckpt: optimize by removing read/writeElement
cfromknecht Jan 25, 2020
f37f475
wire/msgcfcheckpt: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
2383a04
wire/msginv: optimize by reusing small buffers
cfromknecht Jan 25, 2020
d6594da
wire/msggetdata: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
ddeba60
wiree/msggetcfilters: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
834febb
wire/msgcfilter: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
efcf964
wire/msgnotfound: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
1cd5e02
wire/invvect: remove unused readInvVect and writeInvVect
cfromknecht Jan 25, 2020
57daac3
wire/common: add optimized writeVarStrBuf an readVarStrBuf
cfromknecht Jan 25, 2020
dc4fbb0
wire/msgreject: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
8bf07cc
wire/netaddress: add optimiezed read/writeNetAddressBuf
cfromknecht Jan 25, 2020
7207967
wire/msgmerkleblock: optimize serialization by reusing small buffers
cfromknecht Jan 25, 2020
3698f2d
wire/msgping: remove usage for read/writeElement
cfromknecht Jan 25, 2020
80ae5d3
wire/msgpong: remove usage of read/writeElement
cfromknecht Jan 25, 2020
da89ed6
wire/msgtx: remove unused writeTxWitness
cfromknecht Jan 25, 2020
f0184e5
wire/msgtx: remove unused writeTxIn
cfromknecht Jan 25, 2020
e0fa866
wire/msgtx: remove unused readTxIn
cfromknecht Jan 25, 2020
4cc4f76
wire/msgtx: remove unused readScript
cfromknecht Jan 25, 2020
2e6eefc
wire/msgtx: remove unused read/writeOutPoint
cfromknecht Jan 25, 2020
d7396dc
wire/msgtx: use tx-level script slab
cfromknecht Jan 25, 2020
8c4da83
wire/msgblock+msgtx: user block-level script slab
cfromknecht Jan 25, 2020
b0e9636
wire: consistently use defer for returning scratch buffers
Roasbeef Dec 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
475 changes: 470 additions & 5 deletions wire/bench_test.go

Large diffs are not rendered by default.

103 changes: 98 additions & 5 deletions wire/blockheader.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,109 @@ func NewBlockHeader(version int32, prevHash, merkleRootHash *chainhash.Hash,
// readBlockHeader reads a bitcoin block header from r. See Deserialize for
// decoding block headers stored to disk, such as in a database, as opposed to
// decoding from the wire.
//
// DEPRECATED: Use readBlockHeaderBuf instead.
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
return readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot,
(*uint32Time)(&bh.Timestamp), &bh.Bits, &bh.Nonce)
buf := binarySerializer.Borrow()
err := readBlockHeaderBuf(r, pver, bh, buf)
binarySerializer.Return(buf)
return err
}

// readBlockHeaderBuf reads a bitcoin block header from r. See Deserialize for
// decoding block headers stored to disk, such as in a database, as opposed to
// decoding from the wire.
//
// If b is non-nil, the provided buffer will be used for serializing small
// values. Otherwise a buffer will be drawn from the binarySerializer's pool
// and return when the method finishes.
//
// NOTE: b MUST either be nil or at least an 8-byte slice.
func readBlockHeaderBuf(r io.Reader, pver uint32, bh *BlockHeader,
buf []byte) error {

if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
bh.Version = int32(littleEndian.Uint32(buf[:4]))

if _, err := io.ReadFull(r, bh.PrevBlock[:]); err != nil {
return err
}

if _, err := io.ReadFull(r, bh.MerkleRoot[:]); err != nil {
return err
}

if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
bh.Timestamp = time.Unix(int64(littleEndian.Uint32(buf[:4])), 0)

if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
bh.Bits = littleEndian.Uint32(buf[:4])

if _, err := io.ReadFull(r, buf[:4]); err != nil {
return err
}
bh.Nonce = littleEndian.Uint32(buf[:4])

return nil
}

// writeBlockHeader writes a bitcoin block header to w. See Serialize for
// encoding block headers to be stored to disk, such as in a database, as
// opposed to encoding for the wire.
//
// DEPRECATED: Use writeBlockHeaderBuf instead.
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
sec := uint32(bh.Timestamp.Unix())
return writeElements(w, bh.Version, &bh.PrevBlock, &bh.MerkleRoot,
sec, bh.Bits, bh.Nonce)
buf := binarySerializer.Borrow()
err := writeBlockHeaderBuf(w, pver, bh, buf)
binarySerializer.Return(buf)
return err
}

// writeBlockHeaderBuf writes a bitcoin block header to w. See Serialize for
// encoding block headers to be stored to disk, such as in a database, as
// opposed to encoding for the wire.
//
// If b is non-nil, the provided buffer will be used for serializing small
// values. Otherwise a buffer will be drawn from the binarySerializer's pool
// and return when the method finishes.
//
// NOTE: b MUST either be nil or at least an 8-byte slice.
func writeBlockHeaderBuf(w io.Writer, pver uint32, bh *BlockHeader,
buf []byte) error {

littleEndian.PutUint32(buf[:4], uint32(bh.Version))
if _, err := w.Write(buf[:4]); err != nil {
return err
}

if _, err := w.Write(bh.PrevBlock[:]); err != nil {
return err
}

if _, err := w.Write(bh.MerkleRoot[:]); err != nil {
return err
}

littleEndian.PutUint32(buf[:4], uint32(bh.Timestamp.Unix()))
if _, err := w.Write(buf[:4]); err != nil {
return err
}

littleEndian.PutUint32(buf[:4], bh.Bits)
if _, err := w.Write(buf[:4]); err != nil {
return err
}

littleEndian.PutUint32(buf[:4], bh.Nonce)
if _, err := w.Write(buf[:4]); err != nil {
return err
}

return nil
}
167 changes: 128 additions & 39 deletions wire/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,19 +474,29 @@ 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) {
discriminant, err := binarySerializer.Uint8(r)
if err != nil {
buf := binarySerializer.Borrow()
n, err := ReadVarIntBuf(r, pver, buf)
binarySerializer.Return(buf)
return n, err
}

// ReadVarIntBuf reads a variable length integer from r using a preallocated
// scratch buffer and returns it as a uint64.
//
// NOTE: buf MUST at least an 8-byte slice.
func ReadVarIntBuf(r io.Reader, pver uint32, buf []byte) (uint64, error) {
if _, err := io.ReadFull(r, buf[:1]); err != nil {
return 0, err
}
discriminant := buf[0]

var rv uint64
switch discriminant {
case 0xff:
sv, err := binarySerializer.Uint64(r, littleEndian)
if err != nil {
if _, err := io.ReadFull(r, buf); err != nil {
return 0, err
}
rv = sv
rv = littleEndian.Uint64(buf)

// The encoding is not canonical if the value could have been
// encoded using fewer bytes.
Expand All @@ -497,11 +507,10 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
}

case 0xfe:
sv, err := binarySerializer.Uint32(r, littleEndian)
if err != nil {
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return 0, err
}
rv = uint64(sv)
rv = uint64(littleEndian.Uint32(buf[:4]))

// The encoding is not canonical if the value could have been
// encoded using fewer bytes.
Expand All @@ -512,11 +521,10 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
}

case 0xfd:
sv, err := binarySerializer.Uint16(r, littleEndian)
if err != nil {
if _, err := io.ReadFull(r, buf[:2]); err != nil {
return 0, err
}
rv = uint64(sv)
rv = uint64(littleEndian.Uint16(buf[:2]))

// The encoding is not canonical if the value could have been
// encoded using fewer bytes.
Expand All @@ -536,31 +544,45 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
// WriteVarInt serializes val to w using a variable number of bytes depending
// on its value.
func WriteVarInt(w io.Writer, pver uint32, val uint64) error {
if val < 0xfd {
return binarySerializer.PutUint8(w, uint8(val))
}
buf := binarySerializer.Borrow()
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
err := WriteVarIntBuf(w, pver, val, buf)
binarySerializer.Return(buf)
return err
}

if val <= math.MaxUint16 {
err := binarySerializer.PutUint8(w, 0xfd)
if err != nil {
return err
}
return binarySerializer.PutUint16(w, littleEndian, uint16(val))
}
// WriteVarIntBuf serializes val to w using a variable number of bytes depending
// on its value using a preallocated scratch buffer.
//
// NOTE: buf MUST at least an 8-byte slice.
func WriteVarIntBuf(w io.Writer, pver uint32, val uint64, buf []byte) error {
switch {
case val < 0xfd:
buf[0] = uint8(val)
_, err := w.Write(buf[:1])
return err

if val <= math.MaxUint32 {
err := binarySerializer.PutUint8(w, 0xfe)
if err != nil {
case val <= math.MaxUint16:
buf[0] = 0xfd
littleEndian.PutUint16(buf[1:3], uint16(val))
_, err := w.Write(buf[:3])
return err

case val <= math.MaxUint32:
buf[0] = 0xfe
littleEndian.PutUint32(buf[1:5], uint32(val))
_, err := w.Write(buf[:5])
return err

default:
buf[0] = 0xff
if _, err := w.Write(buf[:1]); err != nil {
return err
}
return binarySerializer.PutUint32(w, littleEndian, uint32(val))
}

err := binarySerializer.PutUint8(w, 0xff)
if err != nil {
littleEndian.PutUint64(buf, val)
_, err := w.Write(buf)
return err
}
return binarySerializer.PutUint64(w, littleEndian, val)
}

// VarIntSerializeSize returns the number of bytes it would take to serialize
Expand Down Expand Up @@ -593,7 +615,26 @@ func VarIntSerializeSize(val uint64) int {
// maximum block payload size since it helps protect against memory exhaustion
// attacks and forced panics through malformed messages.
func ReadVarString(r io.Reader, pver uint32) (string, error) {
count, err := ReadVarInt(r, pver)
buf := binarySerializer.Borrow()
str, err := readVarStringBuf(r, pver, buf)
binarySerializer.Return(buf)
return str, err
}

// readVarStringBuf reads a variable length string from r and returns it as a Go
// string. A variable length string is encoded as a variable length integer
// containing the length of the string followed by the bytes that represent the
// string itself. An error is returned if the length is greater than the
// maximum block payload size since it helps protect against memory exhaustion
// attacks and forced panics through malformed messages.
//
// If b is non-nil, the provided buffer will be used for serializing small
// values. Otherwise a buffer will be drawn from the binarySerializer's pool
// and return when the method finishes.
//
// NOTE: b MUST either be nil or at least an 8-byte slice.
func readVarStringBuf(r io.Reader, pver uint32, buf []byte) (string, error) {
count, err := ReadVarIntBuf(r, pver, buf)
if err != nil {
return "", err
}
Expand All @@ -607,22 +648,39 @@ func ReadVarString(r io.Reader, pver uint32) (string, error) {
return "", messageError("ReadVarString", str)
}

buf := make([]byte, count)
_, err = io.ReadFull(r, buf)
str := make([]byte, count)
_, err = io.ReadFull(r, str)
if err != nil {
return "", err
}
return string(buf), nil
return string(str), nil
}

// WriteVarString serializes str to w as a variable length integer containing
// the length of the string followed by the bytes that represent the string
// itself.
func WriteVarString(w io.Writer, pver uint32, str string) error {
err := WriteVarInt(w, pver, uint64(len(str)))
buf := binarySerializer.Borrow()
err := writeVarStringBuf(w, pver, str, buf)
binarySerializer.Return(buf)
return err
}

// writeVarStringBuf serializes str to w as a variable length integer containing
// the length of the string followed by the bytes that represent the string
// itself.
//
// If b is non-nil, the provided buffer will be used for serializing small
// values. Otherwise a buffer will be drawn from the binarySerializer's pool
// and return when the method finishes.
//
// NOTE: b MUST either be nil or at least an 8-byte slice.
func writeVarStringBuf(w io.Writer, pver uint32, str string, buf []byte) error {
err := WriteVarIntBuf(w, pver, uint64(len(str)), buf)
if err != nil {
return err
}

_, err = w.Write([]byte(str))
return err
}
Expand All @@ -637,7 +695,25 @@ func WriteVarString(w io.Writer, pver uint32, str string) error {
func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
fieldName string) ([]byte, error) {

count, err := ReadVarInt(r, pver)
buf := binarySerializer.Borrow()
b, err := ReadVarBytesBuf(r, pver, buf, maxAllowed, fieldName)
binarySerializer.Return(buf)
return b, err
}

// ReadVarBytesBuf reads a variable length byte array. A byte array is encoded
// as a varInt containing the length of the array followed by the bytes
// themselves. An error is returned if the length is greater than the
// passed maxAllowed parameter which helps protect against memory exhaustion
// attacks and forced panics through malformed messages. The fieldName
// parameter is only used for the error message so it provides more context in
// the error. If b is non-nil, the provided buffer will be used for serializing
// small values. Otherwise a buffer will be drawn from the binarySerializer's
// pool and return when the method finishes.
func ReadVarBytesBuf(r io.Reader, pver uint32, buf []byte, maxAllowed uint32,
fieldName string) ([]byte, error) {

count, err := ReadVarIntBuf(r, pver, buf)
if err != nil {
return nil, err
}
Expand All @@ -651,19 +727,32 @@ func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
return nil, messageError("ReadVarBytes", str)
}

b := make([]byte, count)
_, err = io.ReadFull(r, b)
bytes := make([]byte, count)
_, err = io.ReadFull(r, bytes)
if err != nil {
return nil, err
}
return b, nil
return bytes, nil
}

// WriteVarBytes serializes a variable length byte array to w as a varInt
// containing the number of bytes, followed by the bytes themselves.
func WriteVarBytes(w io.Writer, pver uint32, bytes []byte) error {
buf := binarySerializer.Borrow()
err := WriteVarBytesBuf(w, pver, bytes, buf)
binarySerializer.Return(buf)
return err
}

// WriteVarBytesBuf serializes a variable length byte array to w as a varInt
// containing the number of bytes, followed by the bytes themselves. If b is
// non-nil, the provided buffer will be used for serializing small values.
// Otherwise a buffer will be drawn from the binarySerializer's pool and return
// when the method finishes.
func WriteVarBytesBuf(w io.Writer, pver uint32, bytes, buf []byte) error {
slen := uint64(len(bytes))
err := WriteVarInt(w, pver, slen)

err := WriteVarIntBuf(w, pver, slen, buf)
if err != nil {
return err
}
Expand Down