diff --git a/wire/bench_test.go b/wire/bench_test.go index f6637d4210..ff2313e6af 100644 --- a/wire/bench_test.go +++ b/wire/bench_test.go @@ -63,6 +63,8 @@ var genesisCoinbaseTx = MsgTx{ // BenchmarkWriteVarInt1 performs a benchmark on how long it takes to write // a single byte variable length integer. func BenchmarkWriteVarInt1(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarInt(ioutil.Discard, 0, 1) } @@ -71,6 +73,8 @@ func BenchmarkWriteVarInt1(b *testing.B) { // BenchmarkWriteVarInt3 performs a benchmark on how long it takes to write // a three byte variable length integer. func BenchmarkWriteVarInt3(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarInt(ioutil.Discard, 0, 65535) } @@ -79,6 +83,8 @@ func BenchmarkWriteVarInt3(b *testing.B) { // BenchmarkWriteVarInt5 performs a benchmark on how long it takes to write // a five byte variable length integer. func BenchmarkWriteVarInt5(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarInt(ioutil.Discard, 0, 4294967295) } @@ -87,6 +93,8 @@ func BenchmarkWriteVarInt5(b *testing.B) { // BenchmarkWriteVarInt9 performs a benchmark on how long it takes to write // a nine byte variable length integer. func BenchmarkWriteVarInt9(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarInt(ioutil.Discard, 0, 18446744073709551615) } @@ -95,6 +103,8 @@ func BenchmarkWriteVarInt9(b *testing.B) { // BenchmarkReadVarInt1 performs a benchmark on how long it takes to read // a single byte variable length integer. func BenchmarkReadVarInt1(b *testing.B) { + b.ReportAllocs() + buf := []byte{0x01} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -106,6 +116,8 @@ func BenchmarkReadVarInt1(b *testing.B) { // BenchmarkReadVarInt3 performs a benchmark on how long it takes to read // a three byte variable length integer. func BenchmarkReadVarInt3(b *testing.B) { + b.ReportAllocs() + buf := []byte{0x0fd, 0xff, 0xff} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -117,6 +129,8 @@ func BenchmarkReadVarInt3(b *testing.B) { // BenchmarkReadVarInt5 performs a benchmark on how long it takes to read // a five byte variable length integer. func BenchmarkReadVarInt5(b *testing.B) { + b.ReportAllocs() + buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -128,6 +142,8 @@ func BenchmarkReadVarInt5(b *testing.B) { // BenchmarkReadVarInt9 performs a benchmark on how long it takes to read // a nine byte variable length integer. func BenchmarkReadVarInt9(b *testing.B) { + b.ReportAllocs() + buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -136,9 +152,119 @@ func BenchmarkReadVarInt9(b *testing.B) { } } +// BenchmarkWriteVarIntBuf1 performs a benchmark on how long it takes to write +// a single byte variable length integer. +func BenchmarkWriteVarIntBuf1(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + WriteVarIntBuf(ioutil.Discard, 0, 1, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkWriteVarIntBuf3 performs a benchmark on how long it takes to write +// a three byte variable length integer. +func BenchmarkWriteVarIntBuf3(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + WriteVarIntBuf(ioutil.Discard, 0, 65535, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkWriteVarIntBuf5 performs a benchmark on how long it takes to write +// a five byte variable length integer. +func BenchmarkWriteVarIntBuf5(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + WriteVarIntBuf(ioutil.Discard, 0, 4294967295, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkWriteVarIntBuf9 performs a benchmark on how long it takes to write +// a nine byte variable length integer. +func BenchmarkWriteVarIntBuf9(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + WriteVarIntBuf(ioutil.Discard, 0, 18446744073709551615, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkReadVarIntBuf1 performs a benchmark on how long it takes to read +// a single byte variable length integer. +func BenchmarkReadVarIntBuf1(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0x01} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + ReadVarIntBuf(r, 0, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkReadVarIntBuf3 performs a benchmark on how long it takes to read +// a three byte variable length integer. +func BenchmarkReadVarIntBuf3(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0x0fd, 0xff, 0xff} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + ReadVarIntBuf(r, 0, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkReadVarIntBuf5 performs a benchmark on how long it takes to read +// a five byte variable length integer. +func BenchmarkReadVarIntBuf5(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + ReadVarIntBuf(r, 0, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkReadVarIntBuf9 performs a benchmark on how long it takes to read +// a nine byte variable length integer. +func BenchmarkReadVarIntBuf9(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + ReadVarIntBuf(r, 0, buffer) + } + binarySerializer.Return(buffer) +} + // BenchmarkReadVarStr4 performs a benchmark on how long it takes to read a // four byte variable length string. func BenchmarkReadVarStr4(b *testing.B) { + b.ReportAllocs() + buf := []byte{0x04, 't', 'e', 's', 't'} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -150,6 +276,8 @@ func BenchmarkReadVarStr4(b *testing.B) { // BenchmarkReadVarStr10 performs a benchmark on how long it takes to read a // ten byte variable length string. func BenchmarkReadVarStr10(b *testing.B) { + b.ReportAllocs() + buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'} r := bytes.NewReader(buf) for i := 0; i < b.N; i++ { @@ -161,6 +289,8 @@ func BenchmarkReadVarStr10(b *testing.B) { // BenchmarkWriteVarStr4 performs a benchmark on how long it takes to write a // four byte variable length string. func BenchmarkWriteVarStr4(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarString(ioutil.Discard, 0, "test") } @@ -169,14 +299,73 @@ func BenchmarkWriteVarStr4(b *testing.B) { // BenchmarkWriteVarStr10 performs a benchmark on how long it takes to write a // ten byte variable length string. func BenchmarkWriteVarStr10(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { WriteVarString(ioutil.Discard, 0, "test012345") } } +// BenchmarkReadVarStrBuf4 performs a benchmark on how long it takes to read a +// four byte variable length string. +func BenchmarkReadVarStrBuf4(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0x04, 't', 'e', 's', 't'} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + readVarStringBuf(r, 0, buffer) + } + binarySerializer.Return(buffer) +} + +// BenchmarkReadVarStrBuf10 performs a benchmark on how long it takes to read a +// ten byte variable length string. +func BenchmarkReadVarStrBuf10(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'} + r := bytes.NewReader(buf) + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + readVarStringBuf(r, 0, buf) + } + binarySerializer.Return(buffer) +} + +// BenchmarkWriteVarStrBuf4 performs a benchmark on how long it takes to write a +// four byte variable length string. +func BenchmarkWriteVarStrBuf4(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + writeVarStringBuf(ioutil.Discard, 0, "test", buf) + } + binarySerializer.Return(buf) +} + +// BenchmarkWriteVarStrBuf10 performs a benchmark on how long it takes to write +// a ten byte variable length string. +func BenchmarkWriteVarStrBuf10(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() + for i := 0; i < b.N; i++ { + writeVarStringBuf(ioutil.Discard, 0, "test012345", buf) + } + binarySerializer.Return(buf) +} + // BenchmarkReadOutPoint performs a benchmark on how long it takes to read a // transaction output point. func BenchmarkReadOutPoint(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() buf := []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -188,25 +377,35 @@ func BenchmarkReadOutPoint(b *testing.B) { var op OutPoint for i := 0; i < b.N; i++ { r.Seek(0, 0) - readOutPoint(r, 0, 0, &op) + readOutPointBuf(r, 0, 0, &op, buffer) } + binarySerializer.Return(buffer) } // BenchmarkWriteOutPoint performs a benchmark on how long it takes to write a // transaction output point. func BenchmarkWriteOutPoint(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() op := &OutPoint{ Hash: chainhash.Hash{}, Index: 0, } for i := 0; i < b.N; i++ { - writeOutPoint(ioutil.Discard, 0, 0, op) + writeOutPointBuf(ioutil.Discard, 0, 0, op, buf) } + binarySerializer.Return(buf) } // BenchmarkReadTxOut performs a benchmark on how long it takes to read a // transaction output. func BenchmarkReadTxOut(b *testing.B) { + b.ReportAllocs() + + scriptBuffer := scriptPool.Borrow() + sbuf := scriptBuffer[:] + buffer := binarySerializer.Borrow() buf := []byte{ 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount 0x43, // Varint for length of pk script @@ -226,23 +425,44 @@ func BenchmarkReadTxOut(b *testing.B) { var txOut TxOut for i := 0; i < b.N; i++ { r.Seek(0, 0) - readTxOut(r, 0, 0, &txOut) - scriptPool.Return(txOut.PkScript) + readTxOutBuf(r, 0, 0, &txOut, buffer, sbuf) } + binarySerializer.Return(buffer) + scriptPool.Return(scriptBuffer) } // BenchmarkWriteTxOut performs a benchmark on how long it takes to write // a transaction output. func BenchmarkWriteTxOut(b *testing.B) { + b.ReportAllocs() + txOut := blockOne.Transactions[0].TxOut[0] for i := 0; i < b.N; i++ { WriteTxOut(ioutil.Discard, 0, 0, txOut) } } +// BenchmarkWriteTxOutBuf performs a benchmark on how long it takes to write +// a transaction output. +func BenchmarkWriteTxOutBuf(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() + txOut := blockOne.Transactions[0].TxOut[0] + for i := 0; i < b.N; i++ { + WriteTxOutBuf(ioutil.Discard, 0, 0, txOut, buf) + } + binarySerializer.Return(buf) +} + // BenchmarkReadTxIn performs a benchmark on how long it takes to read a // transaction input. func BenchmarkReadTxIn(b *testing.B) { + b.ReportAllocs() + + scriptBuffer := scriptPool.Borrow() + sbuf := scriptBuffer[:] + buffer := binarySerializer.Borrow() buf := []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -257,18 +477,23 @@ func BenchmarkReadTxIn(b *testing.B) { var txIn TxIn for i := 0; i < b.N; i++ { r.Seek(0, 0) - readTxIn(r, 0, 0, &txIn) - scriptPool.Return(txIn.SignatureScript) + readTxInBuf(r, 0, 0, &txIn, buffer, sbuf) } + binarySerializer.Return(buffer) + scriptPool.Return(scriptBuffer) } // BenchmarkWriteTxIn performs a benchmark on how long it takes to write // a transaction input. func BenchmarkWriteTxIn(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() txIn := blockOne.Transactions[0].TxIn[0] for i := 0; i < b.N; i++ { - writeTxIn(ioutil.Discard, 0, 0, txIn) + writeTxInBuf(ioutil.Discard, 0, 0, txIn, buf) } + binarySerializer.Return(buf) } // BenchmarkDeserializeTx performs a benchmark on how long it takes to @@ -302,6 +527,9 @@ func BenchmarkDeserializeTxSmall(b *testing.B) { 0x00, 0x00, 0x00, 0x00, // Lock time } + b.ReportAllocs() + b.ResetTimer() + r := bytes.NewReader(buf) var tx MsgTx for i := 0; i < b.N; i++ { @@ -313,6 +541,7 @@ func BenchmarkDeserializeTxSmall(b *testing.B) { // BenchmarkDeserializeTxLarge performs a benchmark on how long it takes to // deserialize a very large transaction. func BenchmarkDeserializeTxLarge(b *testing.B) { + // tx bb41a757f405890fb0f5856228e23b715702d714d59bf2b1feb70d8b2b4e3e08 // from the main block chain. fi, err := os.Open("testdata/megatx.bin.bz2") @@ -325,6 +554,9 @@ func BenchmarkDeserializeTxLarge(b *testing.B) { b.Fatalf("Failed to read transaction data: %v", err) } + b.ReportAllocs() + b.ResetTimer() + r := bytes.NewReader(buf) var tx MsgTx for i := 0; i < b.N; i++ { @@ -333,9 +565,64 @@ func BenchmarkDeserializeTxLarge(b *testing.B) { } } +func BenchmarkDeserializeBlock(b *testing.B) { + f, err := os.Open( + "testdata/block-00000000000000000021868c2cefc52a480d173c849412fe81c4e5ab806f94ab.blk", + ) + if err != nil { + b.Fatalf("Failed to open block file: %v", err) + } + defer f.Close() + + buf, err := ioutil.ReadAll(f) + if err != nil { + b.Fatalf("Failed to read block data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + + r := bytes.NewReader(buf) + var block MsgBlock + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + block.Deserialize(r) + } +} + +func BenchmarkSerializeBlock(b *testing.B) { + f, err := os.Open( + "testdata/block-00000000000000000021868c2cefc52a480d173c849412fe81c4e5ab806f94ab.blk", + ) + if err != nil { + b.Fatalf("Failed to open block file: %v", err) + } + defer f.Close() + + buf, err := ioutil.ReadAll(f) + if err != nil { + b.Fatalf("Failed to read block data: %v", err) + } + + var block MsgBlock + err = block.Deserialize(bytes.NewReader(buf)) + if err != nil { + panic(err.Error()) + } + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + block.Serialize(ioutil.Discard) + } +} + // BenchmarkSerializeTx performs a benchmark on how long it takes to serialize // a transaction. func BenchmarkSerializeTx(b *testing.B) { + b.ReportAllocs() + tx := blockOne.Transactions[0] for i := 0; i < b.N; i++ { tx.Serialize(ioutil.Discard) @@ -343,9 +630,79 @@ func BenchmarkSerializeTx(b *testing.B) { } } +// BenchmarkSerializeTxSmall performs a benchmark on how long it takes to +// serialize a transaction. +func BenchmarkSerializeTxSmall(b *testing.B) { + buf := []byte{ + 0x01, 0x00, 0x00, 0x00, // Version + 0x01, // Varint for number of input transactions + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash + 0xff, 0xff, 0xff, 0xff, // Prevous output index + 0x07, // Varint for length of signature script + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script + 0xff, 0xff, 0xff, 0xff, // Sequence + 0x01, // Varint for number of output transactions + 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount + 0x43, // Varint for length of pk script + 0x41, // OP_DATA_65 + 0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c, + 0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16, + 0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c, + 0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c, + 0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4, + 0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6, + 0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e, + 0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58, + 0xee, // 65-byte signature + 0xac, // OP_CHECKSIG + 0x00, 0x00, 0x00, 0x00, // Lock time + } + + var tx MsgTx + tx.Deserialize(bytes.NewReader(buf)) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + tx.Serialize(ioutil.Discard) + } +} + +// BenchmarkSerializeTxLarge performs a benchmark on how long it takes to +// serialize a transaction. +func BenchmarkSerializeTxLarge(b *testing.B) { + // tx bb41a757f405890fb0f5856228e23b715702d714d59bf2b1feb70d8b2b4e3e08 + // from the main block chain. + fi, err := os.Open("testdata/megatx.bin.bz2") + if err != nil { + b.Fatalf("Failed to read transaction data: %v", err) + } + defer fi.Close() + buf, err := ioutil.ReadAll(bzip2.NewReader(fi)) + if err != nil { + b.Fatalf("Failed to read transaction data: %v", err) + } + + var tx MsgTx + tx.Deserialize(bytes.NewReader(buf)) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + tx.Serialize(ioutil.Discard) + } +} + // BenchmarkReadBlockHeader performs a benchmark on how long it takes to // deserialize a block header. func BenchmarkReadBlockHeader(b *testing.B) { + b.ReportAllocs() + buf := []byte{ 0x01, 0x00, 0x00, 0x00, // Version 1 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, @@ -369,18 +726,65 @@ func BenchmarkReadBlockHeader(b *testing.B) { } } +// BenchmarkReadBlockHeaderBuf performs a benchmark on how long it takes to +// deserialize a block header. +func BenchmarkReadBlockHeaderBuf(b *testing.B) { + b.ReportAllocs() + + buffer := binarySerializer.Borrow() + buf := []byte{ + 0x01, 0x00, 0x00, 0x00, // Version 1 + 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, + 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f, + 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, + 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock + 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, + 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, + 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, + 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0xf3, 0xe0, 0x01, 0x00, // Nonce + 0x00, // TxnCount Varint + } + r := bytes.NewReader(buf) + var header BlockHeader + for i := 0; i < b.N; i++ { + r.Seek(0, 0) + readBlockHeaderBuf(r, 0, &header, buffer) + } + binarySerializer.Return(buffer) +} + // BenchmarkWriteBlockHeader performs a benchmark on how long it takes to // serialize a block header. func BenchmarkWriteBlockHeader(b *testing.B) { + b.ReportAllocs() + header := blockOne.Header for i := 0; i < b.N; i++ { writeBlockHeader(ioutil.Discard, 0, &header) } } +// BenchmarkWriteBlockHeaderBuf performs a benchmark on how long it takes to +// serialize a block header. +func BenchmarkWriteBlockHeaderBuf(b *testing.B) { + b.ReportAllocs() + + buf := binarySerializer.Borrow() + header := blockOne.Header + for i := 0; i < b.N; i++ { + writeBlockHeaderBuf(ioutil.Discard, 0, &header, buf) + } + binarySerializer.Return(buf) +} + // BenchmarkDecodeGetHeaders performs a benchmark on how long it takes to // decode a getheaders message with the maximum number of block locator hashes. func BenchmarkDecodeGetHeaders(b *testing.B) { + b.ReportAllocs() + // Create a message with the maximum number of block locators. pver := ProtocolVersion var m MsgGetHeaders @@ -411,6 +815,8 @@ func BenchmarkDecodeGetHeaders(b *testing.B) { // BenchmarkDecodeHeaders performs a benchmark on how long it takes to // decode a headers message with the maximum number of headers. func BenchmarkDecodeHeaders(b *testing.B) { + b.ReportAllocs() + // Create a message with the maximum number of headers. pver := ProtocolVersion var m MsgHeaders @@ -441,6 +847,8 @@ func BenchmarkDecodeHeaders(b *testing.B) { // BenchmarkDecodeGetBlocks performs a benchmark on how long it takes to // decode a getblocks message with the maximum number of block locator hashes. func BenchmarkDecodeGetBlocks(b *testing.B) { + b.ReportAllocs() + // Create a message with the maximum number of block locators. pver := ProtocolVersion var m MsgGetBlocks @@ -471,6 +879,8 @@ func BenchmarkDecodeGetBlocks(b *testing.B) { // BenchmarkDecodeAddr performs a benchmark on how long it takes to decode an // addr message with the maximum number of addresses. func BenchmarkDecodeAddr(b *testing.B) { + b.ReportAllocs() + // Create a message with the maximum number of addresses. pver := ProtocolVersion ip := net.ParseIP("127.0.0.1") @@ -516,6 +926,9 @@ func BenchmarkDecodeInv(b *testing.B) { } buf := bb.Bytes() + b.ReportAllocs() + b.ResetTimer() + r := bytes.NewReader(buf) var msg MsgInv b.ResetTimer() @@ -528,6 +941,8 @@ func BenchmarkDecodeInv(b *testing.B) { // BenchmarkDecodeNotFound performs a benchmark on how long it takes to decode // a notfound message with the maximum number of entries. func BenchmarkDecodeNotFound(b *testing.B) { + b.ReportAllocs() + // Create a message with the maximum number of entries. pver := ProtocolVersion var m MsgNotFound @@ -558,6 +973,8 @@ func BenchmarkDecodeNotFound(b *testing.B) { // BenchmarkDecodeMerkleBlock performs a benchmark on how long it takes to // decode a reasonably sized merkleblock message. func BenchmarkDecodeMerkleBlock(b *testing.B) { + b.ReportAllocs() + // Create a message with random data. pver := ProtocolVersion var m MsgMerkleBlock @@ -596,6 +1013,8 @@ func BenchmarkDecodeMerkleBlock(b *testing.B) { // BenchmarkTxHash performs a benchmark on how long it takes to hash a // transaction. func BenchmarkTxHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { genesisCoinbaseTx.TxHash() } @@ -604,6 +1023,8 @@ func BenchmarkTxHash(b *testing.B) { // BenchmarkDoubleHashB performs a benchmark on how long it takes to perform a // double hash returning a byte slice. func BenchmarkDoubleHashB(b *testing.B) { + b.ReportAllocs() + var buf bytes.Buffer if err := genesisCoinbaseTx.Serialize(&buf); err != nil { b.Errorf("Serialize: unexpected error: %v", err) @@ -620,6 +1041,8 @@ func BenchmarkDoubleHashB(b *testing.B) { // BenchmarkDoubleHashH performs a benchmark on how long it takes to perform // a double hash returning a chainhash.Hash. func BenchmarkDoubleHashH(b *testing.B) { + b.ReportAllocs() + var buf bytes.Buffer if err := genesisCoinbaseTx.Serialize(&buf); err != nil { b.Errorf("Serialize: unexpected error: %v", err) diff --git a/wire/blockheader.go b/wire/blockheader.go index 9c9c2237e6..081630f72f 100644 --- a/wire/blockheader.go +++ b/wire/blockheader.go @@ -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 } diff --git a/wire/common.go b/wire/common.go index 42c1797b32..8123e471b9 100644 --- a/wire/common.go +++ b/wire/common.go @@ -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. @@ -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. @@ -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. @@ -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() + 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 @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/wire/invvect.go b/wire/invvect.go index 1e706642b4..c0756a8f1c 100644 --- a/wire/invvect.go +++ b/wire/invvect.go @@ -74,13 +74,37 @@ func NewInvVect(typ InvType, hash *chainhash.Hash) *InvVect { } } -// readInvVect reads an encoded InvVect from r depending on the protocol +// readInvVectBuf reads an encoded InvVect from r depending on the protocol // version. -func readInvVect(r io.Reader, pver uint32, iv *InvVect) error { - return readElements(r, &iv.Type, &iv.Hash) +// +// 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 readInvVectBuf(r io.Reader, pver uint32, iv *InvVect, buf []byte) error { + if _, err := io.ReadFull(r, buf[:4]); err != nil { + return err + } + iv.Type = InvType(littleEndian.Uint32(buf[:4])) + + _, err := io.ReadFull(r, iv.Hash[:]) + return err } -// writeInvVect serializes an InvVect to w depending on the protocol version. -func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error { - return writeElements(w, iv.Type, &iv.Hash) +// writeInvVectBuf serializes an InvVect to w depending on the protocol version. +// +// 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 writeInvVectBuf(w io.Writer, pver uint32, iv *InvVect, buf []byte) error { + littleEndian.PutUint32(buf[:4], uint32(iv.Type)) + if _, err := w.Write(buf[:4]); err != nil { + return err + } + + _, err := w.Write(iv.Hash[:]) + return err } diff --git a/wire/invvect_test.go b/wire/invvect_test.go index 1d02c09817..cf29d0a2ff 100644 --- a/wire/invvect_test.go +++ b/wire/invvect_test.go @@ -238,10 +238,11 @@ func TestInvVectWire(t *testing.T) { } t.Logf("Running %d tests", len(tests)) + var b [8]byte for i, test := range tests { // Encode to wire format. var buf bytes.Buffer - err := writeInvVect(&buf, test.pver, &test.in) + err := writeInvVectBuf(&buf, test.pver, &test.in, b[:]) if err != nil { t.Errorf("writeInvVect #%d error %v", i, err) continue @@ -255,7 +256,7 @@ func TestInvVectWire(t *testing.T) { // Decode the message from wire format. var iv InvVect rbuf := bytes.NewReader(test.buf) - err = readInvVect(rbuf, test.pver, &iv) + err = readInvVectBuf(rbuf, test.pver, &iv, b[:]) if err != nil { t.Errorf("readInvVect #%d error %v", i, err) continue diff --git a/wire/msgblock.go b/wire/msgblock.go index 4172949dc3..6f63e0abd7 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -62,13 +62,17 @@ func (msg *MsgBlock) ClearTransactions() { // See Deserialize for decoding blocks stored to disk, such as in a database, as // opposed to decoding blocks from the wire. func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - err := readBlockHeader(r, pver, &msg.Header) + buf := binarySerializer.Borrow() + + err := readBlockHeaderBuf(r, pver, &msg.Header, buf) if err != nil { + binarySerializer.Return(buf) return err } - txCount, err := ReadVarInt(r, pver) + txCount, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } @@ -76,20 +80,26 @@ func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) er // It would be possible to cause memory exhaustion and panics without // a sane upper bound on this count. if txCount > maxTxPerBlock { + binarySerializer.Return(buf) str := fmt.Sprintf("too many transactions to fit into a block "+ "[count %d, max %d]", txCount, maxTxPerBlock) return messageError("MsgBlock.BtcDecode", str) } + scriptBuf := scriptPool.Borrow() msg.Transactions = make([]*MsgTx, 0, txCount) for i := uint64(0); i < txCount; i++ { tx := MsgTx{} - err := tx.BtcDecode(r, pver, enc) + err := tx.btcDecode(r, pver, enc, buf, scriptBuf[:]) if err != nil { + scriptPool.Return(scriptBuf) + binarySerializer.Return(buf) return err } msg.Transactions = append(msg.Transactions, &tx) } + scriptPool.Return(scriptBuf) + binarySerializer.Return(buf) return nil } @@ -129,16 +139,20 @@ func (msg *MsgBlock) DeserializeNoWitness(r io.Reader) error { func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { fullLen := r.Len() + buf := binarySerializer.Borrow() + // At the current time, there is no difference between the wire encoding // at protocol version 0 and the stable long-term storage format. As // a result, make use of existing wire protocol functions. - err := readBlockHeader(r, 0, &msg.Header) + err := readBlockHeaderBuf(r, 0, &msg.Header, buf) if err != nil { + binarySerializer.Return(buf) return nil, err } - txCount, err := ReadVarInt(r, 0) + txCount, err := ReadVarIntBuf(r, 0, buf) if err != nil { + binarySerializer.Return(buf) return nil, err } @@ -146,11 +160,14 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { // It would be possible to cause memory exhaustion and panics without // a sane upper bound on this count. if txCount > maxTxPerBlock { + binarySerializer.Return(buf) str := fmt.Sprintf("too many transactions to fit into a block "+ "[count %d, max %d]", txCount, maxTxPerBlock) return nil, messageError("MsgBlock.DeserializeTxLoc", str) } + scriptBuf := scriptPool.Borrow() + // Deserialize each transaction while keeping track of its location // within the byte stream. msg.Transactions = make([]*MsgTx, 0, txCount) @@ -158,13 +175,17 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { for i := uint64(0); i < txCount; i++ { txLocs[i].TxStart = fullLen - r.Len() tx := MsgTx{} - err := tx.Deserialize(r) + err := tx.btcDecode(r, 0, WitnessEncoding, buf, scriptBuf[:]) if err != nil { + scriptPool.Return(scriptBuf) + binarySerializer.Return(buf) return nil, err } msg.Transactions = append(msg.Transactions, &tx) txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart } + scriptPool.Return(scriptBuf) + binarySerializer.Return(buf) return txLocs, nil } @@ -174,23 +195,30 @@ func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { // See Serialize for encoding blocks to be stored to disk, such as in a // database, as opposed to encoding blocks for the wire. func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error { - err := writeBlockHeader(w, pver, &msg.Header) + buf := binarySerializer.Borrow() + + err := writeBlockHeaderBuf(w, pver, &msg.Header, buf) if err != nil { + binarySerializer.Return(buf) return err } - err = WriteVarInt(w, pver, uint64(len(msg.Transactions))) + err = WriteVarIntBuf(w, pver, uint64(len(msg.Transactions)), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, tx := range msg.Transactions { - err = tx.BtcEncode(w, pver, enc) + err = tx.btcEncode(w, pver, enc, buf) if err != nil { + binarySerializer.Return(buf) return err } } + binarySerializer.Return(buf) + return nil } diff --git a/wire/msgcfcheckpt.go b/wire/msgcfcheckpt.go index fc3fd53295..da13666180 100644 --- a/wire/msgcfcheckpt.go +++ b/wire/msgcfcheckpt.go @@ -52,23 +52,28 @@ func (msg *MsgCFCheckpt) AddCFHeader(header *chainhash.Hash) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { + buf := binarySerializer.Borrow() + // Read filter type - err := readElement(r, &msg.FilterType) - if err != nil { + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) // Read stop hash - err = readElement(r, &msg.StopHash) - if err != nil { + if _, err := io.ReadFull(r, msg.StopHash[:]); err != nil { + binarySerializer.Return(buf) return err } // Read number of filter headers - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) // Refuse to decode an insane number of cfheaders. if count > maxCFHeadersLen { @@ -80,7 +85,7 @@ func (msg *MsgCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) msg.FilterHeaders = make([]*chainhash.Hash, count) for i := uint64(0); i < count; i++ { var cfh chainhash.Hash - err := readElement(r, &cfh) + _, err := io.ReadFull(r, cfh[:]) if err != nil { return err } @@ -93,27 +98,32 @@ func (msg *MsgCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgCFCheckpt) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { + buf := binarySerializer.Borrow() + // Write filter type - err := writeElement(w, msg.FilterType) - if err != nil { + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } // Write stop hash - err = writeElement(w, msg.StopHash) - if err != nil { + if _, err := w.Write(msg.StopHash[:]); err != nil { + binarySerializer.Return(buf) return err } // Write length of FilterHeaders slice count := len(msg.FilterHeaders) - err = WriteVarInt(w, pver, uint64(count)) + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) for _, cfh := range msg.FilterHeaders { - err := writeElement(w, cfh) + _, err := w.Write(cfh[:]) if err != nil { return err } diff --git a/wire/msgcfheaders.go b/wire/msgcfheaders.go index 40d30f9b46..644cf36a94 100644 --- a/wire/msgcfheaders.go +++ b/wire/msgcfheaders.go @@ -48,29 +48,34 @@ func (msg *MsgCFHeaders) AddCFHash(hash *chainhash.Hash) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { + buf := binarySerializer.Borrow() + // Read filter type - err := readElement(r, &msg.FilterType) - if err != nil { + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) // Read stop hash - err = readElement(r, &msg.StopHash) - if err != nil { + if _, err := io.ReadFull(r, msg.StopHash[:]); err != nil { + binarySerializer.Return(buf) return err } // Read prev filter header - err = readElement(r, &msg.PrevFilterHeader) - if err != nil { + if _, err := io.ReadFull(r, msg.PrevFilterHeader[:]); err != nil { + binarySerializer.Return(buf) return err } // Read number of filter headers - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) // Limit to max committed filter headers per message. if count > MaxCFHeadersPerMsg { @@ -85,7 +90,7 @@ func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) msg.FilterHashes = make([]*chainhash.Hash, 0, count) for i := uint64(0); i < count; i++ { var cfh chainhash.Hash - err := readElement(r, &cfh) + _, err := io.ReadFull(r, cfh[:]) if err != nil { return err } @@ -98,40 +103,44 @@ func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgCFHeaders) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { + count := len(msg.FilterHashes) + if count > MaxCFHeadersPerMsg { + str := fmt.Sprintf("too many committed filter headers for "+ + "message [count %v, max %v]", count, + MaxBlockHeadersPerMsg) + return messageError("MsgCFHeaders.BtcEncode", str) + } + + buf := binarySerializer.Borrow() + // Write filter type - err := writeElement(w, msg.FilterType) - if err != nil { + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } // Write stop hash - err = writeElement(w, msg.StopHash) - if err != nil { + if _, err := w.Write(msg.StopHash[:]); err != nil { + binarySerializer.Return(buf) return err } // Write prev filter header - err = writeElement(w, msg.PrevFilterHeader) - if err != nil { + if _, err := w.Write(msg.PrevFilterHeader[:]); err != nil { + binarySerializer.Return(buf) return err } - // Limit to max committed headers per message. - count := len(msg.FilterHashes) - if count > MaxCFHeadersPerMsg { - str := fmt.Sprintf("too many committed filter headers for "+ - "message [count %v, max %v]", count, - MaxBlockHeadersPerMsg) - return messageError("MsgCFHeaders.BtcEncode", str) - } - - err = WriteVarInt(w, pver, uint64(count)) + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) for _, cfh := range msg.FilterHashes { - err := writeElement(w, cfh) + _, err := w.Write(cfh[:]) if err != nil { return err } diff --git a/wire/msgcfilter.go b/wire/msgcfilter.go index 097590b2ce..d691013bda 100644 --- a/wire/msgcfilter.go +++ b/wire/msgcfilter.go @@ -38,20 +38,24 @@ type MsgCFilter struct { // This is part of the Message interface implementation. func (msg *MsgCFilter) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { // Read filter type - err := readElement(r, &msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) // Read the hash of the filter's block - err = readElement(r, &msg.BlockHash) - if err != nil { + if _, err := io.ReadFull(r, msg.BlockHash[:]); err != nil { + binarySerializer.Return(buf) return err } // Read filter data - msg.Data, err = ReadVarBytes(r, pver, MaxCFilterDataSize, + var err error + msg.Data, err = ReadVarBytesBuf(r, pver, buf, MaxCFilterDataSize, "cfilter data") + binarySerializer.Return(buf) return err } @@ -65,17 +69,21 @@ func (msg *MsgCFilter) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) er return messageError("MsgCFilter.BtcEncode", str) } - err := writeElement(w, msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } - err = writeElement(w, msg.BlockHash) - if err != nil { + if _, err := w.Write(msg.BlockHash[:]); err != nil { + binarySerializer.Return(buf) return err } - return WriteVarBytes(w, pver, msg.Data) + err := WriteVarBytesBuf(w, pver, msg.Data, buf) + binarySerializer.Return(buf) + return err } // Deserialize decodes a filter from r into the receiver using a format that is diff --git a/wire/msggetblocks.go b/wire/msggetblocks.go index caf4400ca4..b6943be415 100644 --- a/wire/msggetblocks.go +++ b/wire/msggetblocks.go @@ -51,16 +51,21 @@ func (msg *MsgGetBlocks) AddBlockLocatorHash(hash *chainhash.Hash) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - err := readElement(r, &msg.ProtocolVersion) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + msg.ProtocolVersion = littleEndian.Uint32(buf[:4]) // Read num block locator hashes and limit to max. - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) + if count > MaxBlockLocatorsPerMsg { str := fmt.Sprintf("too many block locator hashes for message "+ "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) @@ -73,14 +78,15 @@ func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding msg.BlockLocatorHashes = make([]*chainhash.Hash, 0, count) for i := uint64(0); i < count; i++ { hash := &locatorHashes[i] - err := readElement(r, hash) + _, err := io.ReadFull(r, hash[:]) if err != nil { return err } msg.AddBlockLocatorHash(hash) } - return readElement(r, &msg.HashStop) + _, err = io.ReadFull(r, msg.HashStop[:]) + return err } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. @@ -93,24 +99,29 @@ func (msg *MsgGetBlocks) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding return messageError("MsgGetBlocks.BtcEncode", str) } - err := writeElement(w, msg.ProtocolVersion) - if err != nil { + buf := binarySerializer.Borrow() + littleEndian.PutUint32(buf[:4], msg.ProtocolVersion) + if _, err := w.Write(buf[:4]); err != nil { + binarySerializer.Return(buf) return err } - err = WriteVarInt(w, pver, uint64(count)) + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) for _, hash := range msg.BlockLocatorHashes { - err = writeElement(w, hash) + _, err := w.Write(hash[:]) if err != nil { return err } } - return writeElement(w, &msg.HashStop) + _, err = w.Write(msg.HashStop[:]) + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msggetcfcheckpt.go b/wire/msggetcfcheckpt.go index c30a86cecd..c503fa24e0 100644 --- a/wire/msggetcfcheckpt.go +++ b/wire/msggetcfcheckpt.go @@ -21,23 +21,31 @@ type MsgGetCFCheckpt struct { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { - err := readElement(r, &msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) + binarySerializer.Return(buf) - return readElement(r, &msg.StopHash) + _, err := io.ReadFull(r, msg.StopHash[:]) + return err } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgGetCFCheckpt) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { - err := writeElement(w, msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) - return writeElement(w, &msg.StopHash) + _, err := w.Write(msg.StopHash[:]) + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msggetcfheaders.go b/wire/msggetcfheaders.go index 03a1caf72f..1b1a904b44 100644 --- a/wire/msggetcfheaders.go +++ b/wire/msggetcfheaders.go @@ -22,33 +22,43 @@ type MsgGetCFHeaders struct { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { - err := readElement(r, &msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) - err = readElement(r, &msg.StartHeight) - if err != nil { + if _, err := io.ReadFull(r, buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + msg.StartHeight = littleEndian.Uint32(buf[:4]) + binarySerializer.Return(buf) - return readElement(r, &msg.StopHash) + _, err := io.ReadFull(r, msg.StopHash[:]) + return err } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgGetCFHeaders) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { - err := writeElement(w, msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } - err = writeElement(w, &msg.StartHeight) - if err != nil { + littleEndian.PutUint32(buf[:4], msg.StartHeight) + if _, err := w.Write(buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) - return writeElement(w, &msg.StopHash) + _, err := w.Write(msg.StopHash[:]) + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msggetcfilters.go b/wire/msggetcfilters.go index 8002413826..ba76b653ba 100644 --- a/wire/msggetcfilters.go +++ b/wire/msggetcfilters.go @@ -26,33 +26,43 @@ type MsgGetCFilters struct { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetCFilters) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { - err := readElement(r, &msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.FilterType = FilterType(buf[0]) - err = readElement(r, &msg.StartHeight) - if err != nil { + if _, err := io.ReadFull(r, buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + msg.StartHeight = littleEndian.Uint32(buf[:4]) + binarySerializer.Return(buf) - return readElement(r, &msg.StopHash) + _, err := io.ReadFull(r, msg.StopHash[:]) + return err } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. func (msg *MsgGetCFilters) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { - err := writeElement(w, msg.FilterType) - if err != nil { + buf := binarySerializer.Borrow() + buf[0] = byte(msg.FilterType) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } - err = writeElement(w, &msg.StartHeight) - if err != nil { + littleEndian.PutUint32(buf[:4], msg.StartHeight) + if _, err := w.Write(buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) - return writeElement(w, &msg.StopHash) + _, err := w.Write(msg.StopHash[:]) + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msggetdata.go b/wire/msggetdata.go index 5837fac5ba..da4de87d95 100644 --- a/wire/msggetdata.go +++ b/wire/msggetdata.go @@ -38,13 +38,16 @@ func (msg *MsgGetData) AddInvVect(iv *InvVect) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - count, err := ReadVarInt(r, pver) + buf := binarySerializer.Borrow() + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } // Limit to max inventory vectors per message. if count > MaxInvPerMsg { + binarySerializer.Return(buf) str := fmt.Sprintf("too many invvect in message [%v]", count) return messageError("MsgGetData.BtcDecode", str) } @@ -55,12 +58,14 @@ func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := &invList[i] - err := readInvVect(r, pver, iv) + err := readInvVectBuf(r, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } msg.AddInvVect(iv) } + binarySerializer.Return(buf) return nil } @@ -75,17 +80,21 @@ func (msg *MsgGetData) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) return messageError("MsgGetData.BtcEncode", str) } - err := WriteVarInt(w, pver, uint64(count)) + buf := binarySerializer.Borrow() + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, iv := range msg.InvList { - err := writeInvVect(w, pver, iv) + err := writeInvVectBuf(w, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } } + binarySerializer.Return(buf) return nil } diff --git a/wire/msggetheaders.go b/wire/msggetheaders.go index 0bbe42cb03..dba6eebcc4 100644 --- a/wire/msggetheaders.go +++ b/wire/msggetheaders.go @@ -48,16 +48,20 @@ func (msg *MsgGetHeaders) AddBlockLocatorHash(hash *chainhash.Hash) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - err := readElement(r, &msg.ProtocolVersion) - if err != nil { + buf := binarySerializer.Borrow() + if _, err := io.ReadFull(r, buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + msg.ProtocolVersion = littleEndian.Uint32(buf[:4]) // Read num block locator hashes and limit to max. - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) + binarySerializer.Return(buf) if err != nil { return err } + if count > MaxBlockLocatorsPerMsg { str := fmt.Sprintf("too many block locator hashes for message "+ "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) @@ -70,14 +74,15 @@ func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32, enc MessageEncodin msg.BlockLocatorHashes = make([]*chainhash.Hash, 0, count) for i := uint64(0); i < count; i++ { hash := &locatorHashes[i] - err := readElement(r, hash) + _, err := io.ReadFull(r, hash[:]) if err != nil { return err } msg.AddBlockLocatorHash(hash) } - return readElement(r, &msg.HashStop) + _, err = io.ReadFull(r, msg.HashStop[:]) + return err } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. @@ -91,24 +96,28 @@ func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32, enc MessageEncodin return messageError("MsgGetHeaders.BtcEncode", str) } - err := writeElement(w, msg.ProtocolVersion) - if err != nil { + buf := binarySerializer.Borrow() + littleEndian.PutUint32(buf[:4], msg.ProtocolVersion) + if _, err := w.Write(buf[:4]); err != nil { + binarySerializer.Return(buf) return err } - err = WriteVarInt(w, pver, uint64(count)) + err := WriteVarIntBuf(w, pver, uint64(count), buf) + binarySerializer.Return(buf) if err != nil { return err } for _, hash := range msg.BlockLocatorHashes { - err := writeElement(w, hash) + _, err := w.Write(hash[:]) if err != nil { return err } } - return writeElement(w, &msg.HashStop) + _, err = w.Write(msg.HashStop[:]) + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msgheaders.go b/wire/msgheaders.go index 7d18d930e0..d735c911c1 100644 --- a/wire/msgheaders.go +++ b/wire/msgheaders.go @@ -37,13 +37,16 @@ func (msg *MsgHeaders) AddBlockHeader(bh *BlockHeader) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - count, err := ReadVarInt(r, pver) + buf := binarySerializer.Borrow() + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } // Limit to max block headers per message. if count > MaxBlockHeadersPerMsg { + binarySerializer.Return(buf) str := fmt.Sprintf("too many block headers for message "+ "[count %v, max %v]", count, MaxBlockHeadersPerMsg) return messageError("MsgHeaders.BtcDecode", str) @@ -55,18 +58,21 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) msg.Headers = make([]*BlockHeader, 0, count) for i := uint64(0); i < count; i++ { bh := &headers[i] - err := readBlockHeader(r, pver, bh) + err := readBlockHeaderBuf(r, pver, bh, buf) if err != nil { + binarySerializer.Return(buf) return err } - txCount, err := ReadVarInt(r, pver) + txCount, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } // Ensure the transaction count is zero for headers. if txCount > 0 { + binarySerializer.Return(buf) str := fmt.Sprintf("block headers may not contain "+ "transactions [count %v]", txCount) return messageError("MsgHeaders.BtcDecode", str) @@ -74,6 +80,8 @@ func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) msg.AddBlockHeader(bh) } + binarySerializer.Return(buf) + return nil } @@ -88,14 +96,17 @@ func (msg *MsgHeaders) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) return messageError("MsgHeaders.BtcEncode", str) } - err := WriteVarInt(w, pver, uint64(count)) + buf := binarySerializer.Borrow() + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, bh := range msg.Headers { - err := writeBlockHeader(w, pver, bh) + err := writeBlockHeaderBuf(w, pver, bh, buf) if err != nil { + binarySerializer.Return(buf) return err } @@ -103,12 +114,15 @@ func (msg *MsgHeaders) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) // of transactions on header messages. This is really just an // artifact of the way the original implementation serializes // block headers, but it is required. - err = WriteVarInt(w, pver, 0) + err = WriteVarIntBuf(w, pver, 0, buf) if err != nil { + binarySerializer.Return(buf) return err } } + binarySerializer.Return(buf) + return nil } diff --git a/wire/msginv.go b/wire/msginv.go index 5377b179c3..83216ea8c5 100644 --- a/wire/msginv.go +++ b/wire/msginv.go @@ -46,13 +46,16 @@ func (msg *MsgInv) AddInvVect(iv *InvVect) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - count, err := ReadVarInt(r, pver) + buf := binarySerializer.Borrow() + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } // Limit to max inventory vectors per message. if count > MaxInvPerMsg { + binarySerializer.Return(buf) str := fmt.Sprintf("too many invvect in message [%v]", count) return messageError("MsgInv.BtcDecode", str) } @@ -63,12 +66,14 @@ func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) erro msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := &invList[i] - err := readInvVect(r, pver, iv) + err := readInvVectBuf(r, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } msg.AddInvVect(iv) } + binarySerializer.Return(buf) return nil } @@ -83,17 +88,21 @@ func (msg *MsgInv) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) erro return messageError("MsgInv.BtcEncode", str) } - err := WriteVarInt(w, pver, uint64(count)) + buf := binarySerializer.Borrow() + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, iv := range msg.InvList { - err := writeInvVect(w, pver, iv) + err := writeInvVectBuf(w, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } } + binarySerializer.Return(buf) return nil } diff --git a/wire/msgmerkleblock.go b/wire/msgmerkleblock.go index d2ee472178..be3f73b8b5 100644 --- a/wire/msgmerkleblock.go +++ b/wire/msgmerkleblock.go @@ -49,22 +49,27 @@ func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32, enc MessageEncodi return messageError("MsgMerkleBlock.BtcDecode", str) } - err := readBlockHeader(r, pver, &msg.Header) + buf := binarySerializer.Borrow() + err := readBlockHeaderBuf(r, pver, &msg.Header, buf) if err != nil { + binarySerializer.Return(buf) return err } - err = readElement(r, &msg.Transactions) - if err != nil { + if _, err := io.ReadFull(r, buf[:4]); err != nil { + binarySerializer.Return(buf) return err } + msg.Transactions = littleEndian.Uint32(buf[:4]) // Read num block locator hashes and limit to max. - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } if count > maxTxPerBlock { + binarySerializer.Return(buf) str := fmt.Sprintf("too many transaction hashes for message "+ "[count %v, max %v]", count, maxTxPerBlock) return messageError("MsgMerkleBlock.BtcDecode", str) @@ -76,15 +81,17 @@ func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32, enc MessageEncodi msg.Hashes = make([]*chainhash.Hash, 0, count) for i := uint64(0); i < count; i++ { hash := &hashes[i] - err := readElement(r, hash) + _, err := io.ReadFull(r, hash[:]) if err != nil { + binarySerializer.Return(buf) return err } msg.AddTxHash(hash) } - msg.Flags, err = ReadVarBytes(r, pver, maxFlagsPerMerkleBlock, + msg.Flags, err = ReadVarBytesBuf(r, pver, buf, maxFlagsPerMerkleBlock, "merkle block flags size") + binarySerializer.Return(buf) return err } @@ -111,28 +118,36 @@ func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32, enc MessageEncodi return messageError("MsgMerkleBlock.BtcDecode", str) } - err := writeBlockHeader(w, pver, &msg.Header) + buf := binarySerializer.Borrow() + err := writeBlockHeaderBuf(w, pver, &msg.Header, buf) if err != nil { + binarySerializer.Return(buf) return err } - err = writeElement(w, msg.Transactions) - if err != nil { + littleEndian.PutUint32(buf[:4], msg.Transactions) + if _, err := w.Write(buf[:4]); err != nil { + binarySerializer.Return(buf) return err } - err = WriteVarInt(w, pver, uint64(numHashes)) + err = WriteVarIntBuf(w, pver, uint64(numHashes), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, hash := range msg.Hashes { - err = writeElement(w, hash) + _, err := w.Write(hash[:]) if err != nil { + binarySerializer.Return(buf) return err } } - return WriteVarBytes(w, pver, msg.Flags) + err = WriteVarBytesBuf(w, pver, msg.Flags, buf) + binarySerializer.Return(buf) + + return err } // Command returns the protocol command string for the message. This is part diff --git a/wire/msgnotfound.go b/wire/msgnotfound.go index e867681668..6be7ffb48b 100644 --- a/wire/msgnotfound.go +++ b/wire/msgnotfound.go @@ -35,13 +35,16 @@ func (msg *MsgNotFound) AddInvVect(iv *InvVect) error { // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { - count, err := ReadVarInt(r, pver) + buf := binarySerializer.Borrow() + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } // Limit to max inventory vectors per message. if count > MaxInvPerMsg { + binarySerializer.Return(buf) str := fmt.Sprintf("too many invvect in message [%v]", count) return messageError("MsgNotFound.BtcDecode", str) } @@ -52,12 +55,14 @@ func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) msg.InvList = make([]*InvVect, 0, count) for i := uint64(0); i < count; i++ { iv := &invList[i] - err := readInvVect(r, pver, iv) + err := readInvVectBuf(r, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } msg.AddInvVect(iv) } + binarySerializer.Return(buf) return nil } @@ -72,17 +77,21 @@ func (msg *MsgNotFound) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) return messageError("MsgNotFound.BtcEncode", str) } - err := WriteVarInt(w, pver, uint64(count)) + buf := binarySerializer.Borrow() + err := WriteVarIntBuf(w, pver, uint64(count), buf) if err != nil { + binarySerializer.Return(buf) return err } for _, iv := range msg.InvList { - err := writeInvVect(w, pver, iv) + err := writeInvVectBuf(w, pver, iv, buf) if err != nil { + binarySerializer.Return(buf) return err } } + binarySerializer.Return(buf) return nil } diff --git a/wire/msgping.go b/wire/msgping.go index b2f346e0e1..dd5e61e6bf 100644 --- a/wire/msgping.go +++ b/wire/msgping.go @@ -32,10 +32,11 @@ func (msg *MsgPing) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) err // NOTE: > is not a mistake here. The BIP0031 was defined as AFTER // the version unlike most others. if pver > BIP0031Version { - err := readElement(r, &msg.Nonce) + nonce, err := binarySerializer.Uint64(r, littleEndian) if err != nil { return err } + msg.Nonce = nonce } return nil @@ -48,7 +49,7 @@ func (msg *MsgPing) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) err // NOTE: > is not a mistake here. The BIP0031 was defined as AFTER // the version unlike most others. if pver > BIP0031Version { - err := writeElement(w, msg.Nonce) + err := binarySerializer.PutUint64(w, littleEndian, msg.Nonce) if err != nil { return err } diff --git a/wire/msgpong.go b/wire/msgpong.go index eec80d8d5b..01e83792ef 100644 --- a/wire/msgpong.go +++ b/wire/msgpong.go @@ -31,7 +31,13 @@ func (msg *MsgPong) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) err return messageError("MsgPong.BtcDecode", str) } - return readElement(r, &msg.Nonce) + nonce, err := binarySerializer.Uint64(r, littleEndian) + if err != nil { + return err + } + msg.Nonce = nonce + + return nil } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. @@ -45,7 +51,7 @@ func (msg *MsgPong) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) err return messageError("MsgPong.BtcEncode", str) } - return writeElement(w, msg.Nonce) + return binarySerializer.PutUint64(w, littleEndian, msg.Nonce) } // Command returns the protocol command string for the message. This is part diff --git a/wire/msgreject.go b/wire/msgreject.go index a00eeff6f6..64c81d8b5b 100644 --- a/wire/msgreject.go +++ b/wire/msgreject.go @@ -81,30 +81,35 @@ func (msg *MsgReject) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) e } // Command that was rejected. - cmd, err := ReadVarString(r, pver) + buf := binarySerializer.Borrow() + cmd, err := readVarStringBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } msg.Cmd = cmd // Code indicating why the command was rejected. - err = readElement(r, &msg.Code) - if err != nil { + if _, err := io.ReadFull(r, buf[:1]); err != nil { + binarySerializer.Return(buf) return err } + msg.Code = RejectCode(buf[0]) // Human readable string with specific details (over and above the // reject code above) about why the command was rejected. - reason, err := ReadVarString(r, pver) + reason, err := readVarStringBuf(r, pver, buf) if err != nil { + binarySerializer.Return(buf) return err } msg.Reason = reason + binarySerializer.Return(buf) // CmdBlock and CmdTx messages have an additional hash field that // identifies the specific block or transaction. if msg.Cmd == CmdBlock || msg.Cmd == CmdTx { - err := readElement(r, &msg.Hash) + _, err := io.ReadFull(r, msg.Hash[:]) if err != nil { return err } @@ -123,28 +128,33 @@ func (msg *MsgReject) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) e } // Command that was rejected. - err := WriteVarString(w, pver, msg.Cmd) + buf := binarySerializer.Borrow() + err := writeVarStringBuf(w, pver, msg.Cmd, buf) if err != nil { + binarySerializer.Return(buf) return err } // Code indicating why the command was rejected. - err = writeElement(w, msg.Code) - if err != nil { + buf[0] = byte(msg.Code) + if _, err := w.Write(buf[:1]); err != nil { + binarySerializer.Return(buf) return err } // Human readable string with specific details (over and above the // reject code above) about why the command was rejected. - err = WriteVarString(w, pver, msg.Reason) + err = writeVarStringBuf(w, pver, msg.Reason, buf) if err != nil { + binarySerializer.Return(buf) return err } + binarySerializer.Return(buf) // CmdBlock and CmdTx messages have an additional hash field that // identifies the specific block or transaction. if msg.Cmd == CmdBlock || msg.Cmd == CmdTx { - err := writeElement(w, &msg.Hash) + _, err := w.Write(msg.Hash[:]) if err != nil { return err } diff --git a/wire/msgtx.go b/wire/msgtx.go index 1e2f69fad4..f3f6407982 100644 --- a/wire/msgtx.go +++ b/wire/msgtx.go @@ -92,7 +92,7 @@ const ( // scripts per transaction being simultaneously deserialized by 125 // peers. Thus, the peak usage of the free list is 12,500 * 512 = // 6,400,000 bytes. - freeListMaxItems = 12500 + freeListMaxItems = 125 // maxWitnessItemsPerInput is the maximum number of witness items to // be read for the witness data for a single TxIn. This number is @@ -142,6 +142,10 @@ const ( WitnessFlag TxFlag = 0x01 ) +const scriptSlabSize = 1 << 22 + +type scriptSlab [scriptSlabSize]byte + // scriptFreeList defines a free list of byte slices (up to the maximum number // defined by the freeListMaxItems constant) that have a cap according to the // freeListMaxScriptSize constant. It is used to provide temporary buffers for @@ -150,7 +154,7 @@ const ( // // The caller can obtain a buffer from the free list by calling the Borrow // function and should return it via the Return function when done using it. -type scriptFreeList chan []byte +type scriptFreeList chan *scriptSlab // Borrow returns a byte slice from the free list with a length according the // provided size. A new buffer is allocated if there are any items available. @@ -159,18 +163,14 @@ type scriptFreeList chan []byte // a new buffer of the appropriate size is allocated and returned. It is safe // to attempt to return said buffer via the Return function as it will be // ignored and allowed to go the garbage collector. -func (c scriptFreeList) Borrow(size uint64) []byte { - if size > freeListMaxScriptSize { - return make([]byte, size) - } - - var buf []byte +func (c scriptFreeList) Borrow() *scriptSlab { + var buf *scriptSlab select { case buf = <-c: default: - buf = make([]byte, freeListMaxScriptSize) + buf = new(scriptSlab) } - return buf[:size] + return buf } // Return puts the provided byte slice back on the free list when it has a cap @@ -178,13 +178,7 @@ func (c scriptFreeList) Borrow(size uint64) []byte { // the Borrow function. Any slices that are not of the appropriate size, such // as those whose size is greater than the largest allowed free list item size // are simply ignored so they can go to the garbage collector. -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 - } - +func (c scriptFreeList) Return(buf *scriptSlab) { // Return the buffer to the free list when it's not full. Otherwise let // it be garbage collected. select { @@ -197,7 +191,7 @@ func (c scriptFreeList) Return(buf []byte) { // Create the concurrent safe free list to use for script deserialization. As // previously described, this free list is maintained to significantly reduce // the number of allocations. -var scriptPool scriptFreeList = make(chan []byte, freeListMaxItems) +var scriptPool = make(scriptFreeList, freeListMaxItems) // OutPoint defines a bitcoin data type that is used to track previous // transaction outputs. @@ -433,13 +427,23 @@ 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) - if err != nil { + buf := binarySerializer.Borrow() + sbuf := scriptPool.Borrow() + err := msg.btcDecode(r, pver, enc, buf, sbuf[:]) + scriptPool.Return(sbuf) + binarySerializer.Return(buf) + return err +} + +func (msg *MsgTx) btcDecode(r io.Reader, pver uint32, enc MessageEncoding, + buf, sbuf []byte) error { + + if _, err := io.ReadFull(r, buf[:4]); err != nil { return err } - msg.Version = int32(version) + msg.Version = int32(littleEndian.Uint32(buf[:4])) - count, err := ReadVarInt(r, pver) + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { return err } @@ -463,7 +467,7 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error // With the Segregated Witness specific fields decoded, we can // now read in the actual txin count. - count, err = ReadVarInt(r, pver) + count, err = ReadVarIntBuf(r, pver, buf) if err != nil { return err } @@ -479,35 +483,6 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error return 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([]TxIn, count) @@ -517,17 +492,16 @@ 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 = readTxInBuf(r, pver, msg.Version, ti, buf, sbuf) if err != nil { - returnScriptBuffers() return err } totalScriptSize += uint64(len(ti.SignatureScript)) + sbuf = sbuf[len(ti.SignatureScript):] } - count, err = ReadVarInt(r, pver) + count, err = ReadVarIntBuf(r, pver, buf) if err != nil { - returnScriptBuffers() return err } @@ -535,7 +509,6 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error // 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) @@ -550,12 +523,12 @@ 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 = readTxOutBuf(r, pver, msg.Version, to, buf, sbuf) if err != nil { - returnScriptBuffers() return err } totalScriptSize += uint64(len(to.PkScript)) + sbuf = sbuf[len(to.PkScript):] } // If the transaction's flag byte isn't 0x00 at this point, then one or @@ -565,16 +538,14 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error // 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 := ReadVarInt(r, pver) + witCount, err := ReadVarIntBuf(r, pver, buf) 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) @@ -586,22 +557,21 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error // item itself. txin.Witness = make([][]byte, witCount) for j := uint64(0); j < witCount; j++ { - txin.Witness[j], err = readScript(r, pver, + txin.Witness[j], err = readScriptBuf(r, pver, buf, sbuf, maxWitnessItemSize, "script witness item") if err != nil { - returnScriptBuffers() return err } totalScriptSize += uint64(len(txin.Witness[j])) + sbuf = sbuf[len(txin.Witness[j]):] } } } - msg.LockTime, err = binarySerializer.Uint32(r, littleEndian) - if err != nil { - returnScriptBuffers() + if _, err := io.ReadFull(r, buf[:4]); err != nil { return err } + msg.LockTime = littleEndian.Uint32(buf[:4]) // Create a single allocation to house all of the scripts and set each // input signature script and output public key script to the @@ -632,9 +602,6 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error 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 @@ -648,10 +615,6 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error 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++ { @@ -666,9 +629,6 @@ func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error 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 @@ -704,8 +664,17 @@ func (msg *MsgTx) DeserializeNoWitness(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, enc MessageEncoding) error { - err := binarySerializer.PutUint32(w, littleEndian, uint32(msg.Version)) - if err != nil { + buf := binarySerializer.Borrow() + err := msg.btcEncode(w, pver, enc, buf) + binarySerializer.Return(buf) + return err +} + +func (msg *MsgTx) btcEncode(w io.Writer, pver uint32, enc MessageEncoding, + buf []byte) error { + + littleEndian.PutUint32(buf[:4], uint32(msg.Version)) + if _, err := w.Write(buf[:4]); err != nil { return err } @@ -725,26 +694,26 @@ func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error } count := uint64(len(msg.TxIn)) - err = WriteVarInt(w, pver, count) + err := WriteVarIntBuf(w, pver, count, buf) if err != nil { return err } for _, ti := range msg.TxIn { - err = writeTxIn(w, pver, msg.Version, ti) + err = writeTxInBuf(w, pver, msg.Version, ti, buf) if err != nil { return err } } count = uint64(len(msg.TxOut)) - err = WriteVarInt(w, pver, count) + err = WriteVarIntBuf(w, pver, count, buf) if err != nil { return err } for _, to := range msg.TxOut { - err = WriteTxOut(w, pver, msg.Version, to) + err = WriteTxOutBuf(w, pver, msg.Version, to, buf) if err != nil { return err } @@ -755,14 +724,16 @@ func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error // within the transaction. if doWitness { for _, ti := range msg.TxIn { - err = writeTxWitness(w, pver, msg.Version, ti.Witness) + err = writeTxWitnessBuf(w, pver, msg.Version, ti.Witness, buf) if err != nil { return err } } } - return binarySerializer.PutUint32(w, littleEndian, msg.LockTime) + littleEndian.PutUint32(buf[:4], msg.LockTime) + _, err = w.Write(buf[:4]) + return err } // HasWitness returns false if none of the inputs within the transaction @@ -919,26 +890,48 @@ func NewMsgTx(version int32) *MsgTx { } } -// readOutPoint reads the next sequence of bytes from r as an OutPoint. -func readOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error { +// readOutPointBuf reads the next sequence of bytes from r as an OutPoint. +// +// 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 readOutPointBuf(r io.Reader, pver uint32, version int32, op *OutPoint, + buf []byte) error { + _, err := io.ReadFull(r, op.Hash[:]) if err != nil { return err } - op.Index, err = binarySerializer.Uint32(r, littleEndian) - return err + if _, err := io.ReadFull(r, buf[:4]); err != nil { + return err + } + op.Index = littleEndian.Uint32(buf[:4]) + + return nil } // writeOutPoint encodes op to the bitcoin protocol encoding for an OutPoint // to w. -func writeOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) 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. +// +// NOTE: b MUST either be nil or at least an 8-byte slice. +func writeOutPointBuf(w io.Writer, pver uint32, version int32, op *OutPoint, + buf []byte) error { + _, err := w.Write(op.Hash[:]) if err != nil { return err } - return binarySerializer.PutUint32(w, littleEndian, op.Index) + littleEndian.PutUint32(buf[:4], op.Index) + _, err = w.Write(buf[:4]) + return err } // readScript reads a variable length byte array that represents a transaction @@ -948,8 +941,14 @@ func writeOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) error // 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. -func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) { - count, err := ReadVarInt(r, pver) +// +// 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 readScriptBuf(r io.Reader, pver uint32, buf, s []byte, maxAllowed uint32, fieldName string) ([]byte, error) { + count, err := ReadVarIntBuf(r, pver, buf) if err != nil { return nil, err } @@ -963,57 +962,81 @@ func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ( return nil, messageError("readScript", str) } - b := scriptPool.Borrow(count) - _, err = io.ReadFull(r, b) + _, err = io.ReadFull(r, s[:count]) if err != nil { - scriptPool.Return(b) return nil, err } - return b, nil + return s[:count], nil } -// readTxIn reads the next sequence of bytes from r as a transaction input +// readTxInBuf reads the next sequence of bytes from r as a transaction input // (TxIn). -func readTxIn(r io.Reader, pver uint32, version int32, ti *TxIn) error { - err := readOutPoint(r, pver, version, &ti.PreviousOutPoint) +// +// 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 readTxInBuf(r io.Reader, pver uint32, version int32, ti *TxIn, + buf, s []byte) error { + + err := readOutPointBuf(r, pver, version, &ti.PreviousOutPoint, buf) if err != nil { return err } - ti.SignatureScript, err = readScript(r, pver, MaxMessagePayload, + ti.SignatureScript, err = readScriptBuf(r, pver, buf, s, MaxMessagePayload, "transaction input signature script") if err != nil { return err } - return readElement(r, &ti.Sequence) + if _, err := io.ReadFull(r, buf[:4]); err != nil { + return err + } + + ti.Sequence = littleEndian.Uint32(buf[:4]) + + return nil } -// writeTxIn encodes ti to the bitcoin protocol encoding for a transaction -// input (TxIn) to w. -func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn) error { - err := writeOutPoint(w, pver, version, &ti.PreviousOutPoint) +// writeTxInBuf encodes ti to the bitcoin protocol encoding for a transaction +// input (TxIn) to w. 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 writeTxInBuf(w io.Writer, pver uint32, version int32, ti *TxIn, + buf []byte) error { + + err := writeOutPointBuf(w, pver, version, &ti.PreviousOutPoint, buf) if err != nil { return err } - err = WriteVarBytes(w, pver, ti.SignatureScript) + err = WriteVarBytesBuf(w, pver, ti.SignatureScript, buf) if err != nil { return err } - return binarySerializer.PutUint32(w, littleEndian, ti.Sequence) + littleEndian.PutUint32(buf[:4], ti.Sequence) + _, err = w.Write(buf[:4]) + + return err } -// 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 { - err := readElement(r, &to.Value) +// readTxOutBuf reads the next sequence of bytes from r as a transaction output +// (TxOut). 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 readTxOutBuf(r io.Reader, pver uint32, version int32, to *TxOut, + buf, s []byte) error { + + _, err := io.ReadFull(r, buf) if err != nil { return err } + to.Value = int64(littleEndian.Uint64(buf)) - to.PkScript, err = readScript(r, pver, MaxMessagePayload, + to.PkScript, err = readScriptBuf(r, pver, buf, s, MaxMessagePayload, "transaction output public key script") return err } @@ -1024,26 +1047,48 @@ func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { // NOTE: This function is exported in order to allow txscript to compute the // new sighashes for witness transactions (BIP0143). func WriteTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error { - err := binarySerializer.PutUint64(w, littleEndian, uint64(to.Value)) + buf := binarySerializer.Borrow() + err := WriteTxOutBuf(w, pver, version, to, buf) + binarySerializer.Return(buf) + return err +} + +// WriteTxOutBuf encodes to into the bitcoin protocol encoding for a transaction +// output (TxOut) to w. 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: This function is exported in order to allow txscript to compute the +// new sighashes for witness transactions (BIP0143). +func WriteTxOutBuf(w io.Writer, pver uint32, version int32, to *TxOut, + buf []byte) error { + + littleEndian.PutUint64(buf, uint64(to.Value)) + _, err := w.Write(buf) if err != nil { return err } - return WriteVarBytes(w, pver, to.PkScript) + return WriteVarBytesBuf(w, pver, to.PkScript, buf) } -// writeTxWitness encodes the bitcoin protocol encoding for a transaction -// input's witness into to w. -func writeTxWitness(w io.Writer, pver uint32, version int32, wit [][]byte) error { - err := WriteVarInt(w, pver, uint64(len(wit))) +// writeTxWitnessBuf encodes the bitcoin protocol encoding for a transaction +// input's witness into to w. 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 writeTxWitnessBuf(w io.Writer, pver uint32, version int32, wit [][]byte, + buf []byte) error { + + err := WriteVarIntBuf(w, pver, uint64(len(wit)), buf) if err != nil { return err } for _, item := range wit { - err = WriteVarBytes(w, pver, item) + err = WriteVarBytesBuf(w, pver, item, buf) if err != nil { return err } } + return nil } diff --git a/wire/netaddress.go b/wire/netaddress.go index 5a2610bccc..9cef72cf10 100644 --- a/wire/netaddress.go +++ b/wire/netaddress.go @@ -5,7 +5,6 @@ package wire import ( - "encoding/binary" "io" "net" "time" @@ -89,31 +88,59 @@ func NewNetAddress(addr *net.TCPAddr, services ServiceFlag) *NetAddress { // version and whether or not the timestamp is included per ts. Some messages // like version do not include the timestamp. func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { - var ip [16]byte + buf := binarySerializer.Borrow() + err := readNetAddressBuf(r, pver, na, ts, buf) + binarySerializer.Return(buf) + return err +} + +// readNetAddressBuf reads an encoded NetAddress from r depending on the +// protocol version and whether or not the timestamp is included per ts. Some +// messages like version do not include the timestamp. +// +// 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 readNetAddressBuf(r io.Reader, pver uint32, na *NetAddress, ts bool, + buf []byte) error { + + var ( + timestamp time.Time + services ServiceFlag + ip [16]byte + 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 // protocol version >= NetAddressTimeVersion if ts && pver >= NetAddressTimeVersion { - err := readElement(r, (*uint32Time)(&na.Timestamp)) - if err != nil { + if _, err := io.ReadFull(r, buf[:4]); err != nil { return err } + timestamp = time.Unix(int64(littleEndian.Uint32(buf[:4])), 0) + } + + if _, err := io.ReadFull(r, buf); err != nil { + return err } + services = ServiceFlag(littleEndian.Uint64(buf)) - err := readElements(r, &na.Services, &ip) - if err != nil { + if _, err := io.ReadFull(r, ip[:]); err != nil { return err } + // Sigh. Bitcoin protocol mixes little and big endian. - port, err := binarySerializer.Uint16(r, bigEndian) - if err != nil { + if _, err := io.ReadFull(r, buf[:2]); err != nil { return err } + port = bigEndian.Uint16(buf[:2]) *na = NetAddress{ - Timestamp: na.Timestamp, - Services: na.Services, + Timestamp: timestamp, + Services: services, IP: net.IP(ip[:]), Port: port, } @@ -124,26 +151,49 @@ func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { // version and whether or not the timestamp is included per ts. Some messages // like version do not include the timestamp. func writeNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error { + buf := binarySerializer.Borrow() + err := writeNetAddressBuf(w, pver, na, ts, buf) + binarySerializer.Return(buf) + return err +} + +// writeNetAddressBuf serializes a NetAddress to w depending on the protocol +// version and whether or not the timestamp is included per ts. Some messages +// like version do not include the timestamp. +// +// 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 writeNetAddressBuf(w io.Writer, pver uint32, na *NetAddress, ts bool, buf []byte) error { // NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will // stop working somewhere around 2106. Also timestamp wasn't added until // until protocol version >= NetAddressTimeVersion. if ts && pver >= NetAddressTimeVersion { - err := writeElement(w, uint32(na.Timestamp.Unix())) - if err != nil { + littleEndian.PutUint32(buf[:4], uint32(na.Timestamp.Unix())) + if _, err := w.Write(buf[:4]); err != nil { return err } } + littleEndian.PutUint64(buf, uint64(na.Services)) + if _, err := w.Write(buf); err != nil { + return err + } + // Ensure to always write 16 bytes even if the ip is nil. var ip [16]byte if na.IP != nil { copy(ip[:], na.IP.To16()) } - err := writeElements(w, na.Services, ip) - if err != nil { + if _, err := w.Write(ip[:]); err != nil { return err } // Sigh. Bitcoin protocol mixes little and big endian. - return binary.Write(w, bigEndian, na.Port) + bigEndian.PutUint16(buf[:2], na.Port) + _, err := w.Write(buf[:2]) + + return err } diff --git a/wire/testdata/block-0000000000000000001602407ac49862a7bca9d00f7f402db20b7be2f5de59d2.blk b/wire/testdata/block-0000000000000000001602407ac49862a7bca9d00f7f402db20b7be2f5de59d2.blk new file mode 100644 index 0000000000..aacdb7aa99 Binary files /dev/null and b/wire/testdata/block-0000000000000000001602407ac49862a7bca9d00f7f402db20b7be2f5de59d2.blk differ diff --git a/wire/testdata/block-00000000000000000021868c2cefc52a480d173c849412fe81c4e5ab806f94ab.blk b/wire/testdata/block-00000000000000000021868c2cefc52a480d173c849412fe81c4e5ab806f94ab.blk new file mode 100644 index 0000000000..cff5e0f35f Binary files /dev/null and b/wire/testdata/block-00000000000000000021868c2cefc52a480d173c849412fe81c4e5ab806f94ab.blk differ