diff --git a/wire/README.md b/wire/README.md new file mode 100644 index 00000000..94b5be11 --- /dev/null +++ b/wire/README.md @@ -0,0 +1,125 @@ +wire +==== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcd.svg)] +(https://travis-ci.org/btcsuite/btcd) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) + +Package wire implements the bitcoin wire protocol. A comprehensive suite of +tests with 100% test coverage is provided to ensure proper functionality. + +There is an associated blog post about the release of this package +[here](https://blog.conformal.com/btcwire-the-bitcoin-wire-protocol-package-from-btcd/). + +This package has intentionally been designed so it can be used as a standalone +package for any projects needing to interface with bitcoin peers at the wire +protocol level. + +## Documentation + +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcd/wire) + +Full `go doc` style documentation for the project can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/btcsuite/btcd/wire + +You can also view the documentation locally once the package is installed with +the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to +http://localhost:6060/pkg/github.com/btcsuite/btcd/wire + +## Installation + +```bash +$ go get github.com/btcsuite/btcd/wire +``` + +## Bitcoin Message Overview + +The bitcoin protocol consists of exchanging messages between peers. Each message +is preceded by a header which identifies information about it such as which +bitcoin network it is a part of, its type, how big it is, and a checksum to +verify validity. All encoding and decoding of message headers is handled by this +package. + +To accomplish this, there is a generic interface for bitcoin messages named +`Message` which allows messages of any type to be read, written, or passed +around through channels, functions, etc. In addition, concrete implementations +of most of the currently supported bitcoin messages are provided. For these +supported messages, all of the details of marshalling and unmarshalling to and +from the wire using bitcoin encoding are handled so the caller doesn't have to +concern themselves with the specifics. + +## Reading Messages Example + +In order to unmarshal bitcoin messages from the wire, use the `ReadMessage` +function. It accepts any `io.Reader`, but typically this will be a `net.Conn` +to a remote node running a bitcoin peer. Example syntax is: + +```Go + // Use the most recent protocol version supported by the package and the + // main bitcoin network. + pver := wire.ProtocolVersion + btcnet := wire.MainNet + + // Reads and validates the next bitcoin message from conn using the + // protocol version pver and the bitcoin network btcnet. The returns + // are a wire.Message, a []byte which contains the unmarshalled + // raw payload, and a possible error. + msg, rawPayload, err := wire.ReadMessage(conn, pver, btcnet) + if err != nil { + // Log and handle the error + } +``` + +See the package documentation for details on determining the message type. + +## Writing Messages Example + +In order to marshal bitcoin messages to the wire, use the `WriteMessage` +function. It accepts any `io.Writer`, but typically this will be a `net.Conn` +to a remote node running a bitcoin peer. Example syntax to request addresses +from a remote peer is: + +```Go + // Use the most recent protocol version supported by the package and the + // main bitcoin network. + pver := wire.ProtocolVersion + btcnet := wire.MainNet + + // Create a new getaddr bitcoin message. + msg := wire.NewMsgGetAddr() + + // Writes a bitcoin message msg to conn using the protocol version + // pver, and the bitcoin network btcnet. The return is a possible + // error. + err := wire.WriteMessage(conn, msg, pver, btcnet) + if err != nil { + // Log and handle the error + } +``` + +## GPG Verification Key + +All official release tags are signed by Conformal so users can ensure the code +has not been tampered with and is coming from Conformal. To verify the +signature perform the following: + +- Download the public key from the Conformal website at + https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt + +- Import the public key into your GPG keyring: + ```bash + gpg --import GIT-GPG-KEY-conformal.txt + ``` + +- Verify the release tag with the following command where `TAG_NAME` is a + placeholder for the specific tag: + ```bash + git tag -v TAG_NAME + ``` + +## License + +Package wire is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/wire/bench_test.go b/wire/bench_test.go new file mode 100644 index 00000000..3c0350ee --- /dev/null +++ b/wire/bench_test.go @@ -0,0 +1,394 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "io/ioutil" + "testing" + "time" +) + +// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for +// the main network, regression test network, and test network (version 3). +var genesisCoinbaseTx = MsgTx{ + Version: 1, + TxIn: []*TxIn{ + { + PreviousOutPoint: OutPoint{ + Hash: ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */ + 0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */ + 0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */ + 0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */ + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */ + 0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */ + 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/ + 0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */ + 0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/ + 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */ + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*TxOut{ + { + Value: 0x12a05f200, + PkScript: []byte{ + 0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */ + 0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */ + 0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */ + 0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */ + 0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */ + 0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */ + 0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */ + 0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */ + 0x1d, 0x5f, 0xac, /* |._.| */ + }, + }, + }, + LockTime: 0, +} + +// blockOne is the first block in the mainnet block chain. +var blockOne = MsgBlock{ + Header: BlockHeader{ + Version: 1, + PrevBlock: ShaHash([HashSize]byte{ // Make go vet happy. + 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, + }), + MerkleRoot: ShaHash([HashSize]byte{ // Make go vet happy. + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, + }), + + Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST + Bits: 0x1d00ffff, // 486604799 + Nonce: 0x9962e301, // 2573394689 + }, + Transactions: []*MsgTx{ + { + Version: 1, + TxIn: []*TxIn{ + { + PreviousOutPoint: OutPoint{ + Hash: ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*TxOut{ + { + Value: 0x12a05f200, + PkScript: []byte{ + 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 + }, + }, + }, + LockTime: 0, + }, + }, +} + +// BenchmarkWriteVarInt1 performs a benchmark on how long it takes to write +// a single byte variable length integer. +func BenchmarkWriteVarInt1(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarInt(ioutil.Discard, 0, 1) + } +} + +// BenchmarkWriteVarInt3 performs a benchmark on how long it takes to write +// a three byte variable length integer. +func BenchmarkWriteVarInt3(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarInt(ioutil.Discard, 0, 65535) + } +} + +// BenchmarkWriteVarInt5 performs a benchmark on how long it takes to write +// a five byte variable length integer. +func BenchmarkWriteVarInt5(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarInt(ioutil.Discard, 0, 4294967295) + } +} + +// BenchmarkWriteVarInt9 performs a benchmark on how long it takes to write +// a nine byte variable length integer. +func BenchmarkWriteVarInt9(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarInt(ioutil.Discard, 0, 18446744073709551615) + } +} + +// BenchmarkReadVarInt1 performs a benchmark on how long it takes to read +// a single byte variable length integer. +func BenchmarkReadVarInt1(b *testing.B) { + buf := []byte{0x01} + for i := 0; i < b.N; i++ { + readVarInt(bytes.NewReader(buf), 0) + } +} + +// BenchmarkReadVarInt3 performs a benchmark on how long it takes to read +// a three byte variable length integer. +func BenchmarkReadVarInt3(b *testing.B) { + buf := []byte{0x0fd, 0xff, 0xff} + for i := 0; i < b.N; i++ { + readVarInt(bytes.NewReader(buf), 0) + } +} + +// BenchmarkReadVarInt5 performs a benchmark on how long it takes to read +// a five byte variable length integer. +func BenchmarkReadVarInt5(b *testing.B) { + buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff} + for i := 0; i < b.N; i++ { + readVarInt(bytes.NewReader(buf), 0) + } +} + +// BenchmarkReadVarInt9 performs a benchmark on how long it takes to read +// a nine byte variable length integer. +func BenchmarkReadVarInt9(b *testing.B) { + buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + for i := 0; i < b.N; i++ { + readVarInt(bytes.NewReader(buf), 0) + } +} + +// BenchmarkReadVarStr4 performs a benchmark on how long it takes to read a +// four byte variable length string. +func BenchmarkReadVarStr4(b *testing.B) { + buf := []byte{0x04, 't', 'e', 's', 't'} + for i := 0; i < b.N; i++ { + readVarString(bytes.NewReader(buf), 0) + } +} + +// BenchmarkReadVarStr10 performs a benchmark on how long it takes to read a +// ten byte variable length string. +func BenchmarkReadVarStr10(b *testing.B) { + buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'} + for i := 0; i < b.N; i++ { + readVarString(bytes.NewReader(buf), 0) + } +} + +// BenchmarkWriteVarStr4 performs a benchmark on how long it takes to write a +// four byte variable length string. +func BenchmarkWriteVarStr4(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarString(ioutil.Discard, 0, "test") + } +} + +// BenchmarkWriteVarStr10 performs a benchmark on how long it takes to write a +// ten byte variable length string. +func BenchmarkWriteVarStr10(b *testing.B) { + for i := 0; i < b.N; i++ { + writeVarString(ioutil.Discard, 0, "test012345") + } +} + +// BenchmarkReadOutPoint performs a benchmark on how long it takes to read a +// transaction output point. +func BenchmarkReadOutPoint(b *testing.B) { + buf := []byte{ + 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, // Previous output index + } + var op OutPoint + for i := 0; i < b.N; i++ { + readOutPoint(bytes.NewReader(buf), 0, 0, &op) + } +} + +// BenchmarkWriteOutPoint performs a benchmark on how long it takes to write a +// transaction output point. +func BenchmarkWriteOutPoint(b *testing.B) { + op := &OutPoint{ + Hash: ShaHash{}, + Index: 0, + } + for i := 0; i < b.N; i++ { + writeOutPoint(ioutil.Discard, 0, 0, op) + } +} + +// BenchmarkReadTxOut performs a benchmark on how long it takes to read a +// transaction output. +func BenchmarkReadTxOut(b *testing.B) { + buf := []byte{ + 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 + } + var txOut TxOut + for i := 0; i < b.N; i++ { + readTxOut(bytes.NewReader(buf), 0, 0, &txOut) + } +} + +// BenchmarkWriteTxOut performs a benchmark on how long it takes to write +// a transaction output. +func BenchmarkWriteTxOut(b *testing.B) { + txOut := blockOne.Transactions[0].TxOut[0] + for i := 0; i < b.N; i++ { + writeTxOut(ioutil.Discard, 0, 0, txOut) + } +} + +// BenchmarkReadTxIn performs a benchmark on how long it takes to read a +// transaction input. +func BenchmarkReadTxIn(b *testing.B) { + buf := []byte{ + 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, // Previous output index + 0x07, // Varint for length of signature script + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script + 0xff, 0xff, 0xff, 0xff, // Sequence + } + var txIn TxIn + for i := 0; i < b.N; i++ { + readTxIn(bytes.NewReader(buf), 0, 0, &txIn) + } +} + +// BenchmarkWriteTxIn performs a benchmark on how long it takes to write +// a transaction input. +func BenchmarkWriteTxIn(b *testing.B) { + txIn := blockOne.Transactions[0].TxIn[0] + for i := 0; i < b.N; i++ { + writeTxIn(ioutil.Discard, 0, 0, txIn) + } +} + +// BenchmarkDeserializeTx performs a benchmark on how long it takes to +// deserialize a transaction. +func BenchmarkDeserializeTx(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 + for i := 0; i < b.N; i++ { + tx.Deserialize(bytes.NewReader(buf)) + + } +} + +// BenchmarkSerializeTx performs a benchmark on how long it takes to serialize +// a transaction. +func BenchmarkSerializeTx(b *testing.B) { + tx := blockOne.Transactions[0] + 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) { + 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 + } + var header BlockHeader + for i := 0; i < b.N; i++ { + readBlockHeader(bytes.NewReader(buf), 0, &header) + } +} + +// BenchmarkWriteBlockHeader performs a benchmark on how long it takes to +// serialize a block header. +func BenchmarkWriteBlockHeader(b *testing.B) { + header := blockOne.Header + for i := 0; i < b.N; i++ { + writeBlockHeader(ioutil.Discard, 0, &header) + } +} + +// BenchmarkTxSha performs a benchmark on how long it takes to hash a +// transaction. +func BenchmarkTxSha(b *testing.B) { + for i := 0; i < b.N; i++ { + genesisCoinbaseTx.TxSha() + } +} diff --git a/wire/blockheader.go b/wire/blockheader.go new file mode 100644 index 00000000..8e3f79b0 --- /dev/null +++ b/wire/blockheader.go @@ -0,0 +1,131 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "io" + "time" +) + +// BlockVersion is the current latest supported block version. +const BlockVersion = 2 + +// Version 4 bytes + Timestamp 4 bytes + Bits 4 bytes + Nonce 4 bytes + +// PrevBlock and MerkleRoot hashes. +const MaxBlockHeaderPayload = 16 + (HashSize * 2) + +// BlockHeader defines information about a block and is used in the bitcoin +// block (MsgBlock) and headers (MsgHeaders) messages. +type BlockHeader struct { + // Version of the block. This is not the same as the protocol version. + Version int32 + + // Hash of the previous block in the block chain. + PrevBlock ShaHash + + // Merkle tree reference to hash of all transactions for the block. + MerkleRoot ShaHash + + // Time the block was created. This is, unfortunately, encoded as a + // uint32 on the wire and therefore is limited to 2106. + Timestamp time.Time + + // Difficulty target for the block. + Bits uint32 + + // Nonce used to generate the block. + Nonce uint32 +} + +// blockHeaderLen is a constant that represents the number of bytes for a block +// header. +const blockHeaderLen = 80 + +// BlockSha computes the block identifier hash for the given block header. +func (h *BlockHeader) BlockSha() (ShaHash, error) { + // Encode the header and run double sha256 everything prior to the + // number of transactions. Ignore the error returns since there is no + // way the encode could fail except being out of memory which would + // cause a run-time panic. Also, SetBytes can't fail here due to the + // fact DoubleSha256 always returns a []byte of the right size + // regardless of input. + var buf bytes.Buffer + var sha ShaHash + _ = writeBlockHeader(&buf, 0, h) + _ = sha.SetBytes(DoubleSha256(buf.Bytes()[0:blockHeaderLen])) + + // Even though this function can't currently fail, it still returns + // a potential error to help future proof the API should a failure + // become possible. + return sha, nil +} + +// Deserialize decodes a block header from r into the receiver using a format +// that is suitable for long-term storage such as a database while respecting +// the Version field. +func (h *BlockHeader) Deserialize(r io.Reader) error { + // 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 readBlockHeader. + return readBlockHeader(r, 0, h) +} + +// Serialize encodes a block header from r into the receiver using a format +// that is suitable for long-term storage such as a database while respecting +// the Version field. +func (h *BlockHeader) Serialize(w io.Writer) error { + // 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 writeBlockHeader. + return writeBlockHeader(w, 0, h) +} + +// NewBlockHeader returns a new BlockHeader using the provided previous block +// hash, merkle root hash, difficulty bits, and nonce used to generate the +// block with defaults for the remaining fields. +func NewBlockHeader(prevHash *ShaHash, merkleRootHash *ShaHash, bits uint32, + nonce uint32) *BlockHeader { + + // Limit the timestamp to one second precision since the protocol + // doesn't support better. + return &BlockHeader{ + Version: BlockVersion, + PrevBlock: *prevHash, + MerkleRoot: *merkleRootHash, + Timestamp: time.Unix(time.Now().Unix(), 0), + Bits: bits, + Nonce: nonce, + } +} + +// 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. +func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error { + var sec uint32 + err := readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot, &sec, + &bh.Bits, &bh.Nonce) + if err != nil { + return err + } + bh.Timestamp = time.Unix(int64(sec), 0) + + 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. +func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error { + sec := uint32(bh.Timestamp.Unix()) + err := writeElements(w, bh.Version, &bh.PrevBlock, &bh.MerkleRoot, + sec, bh.Bits, bh.Nonce) + if err != nil { + return err + } + + return nil +} diff --git a/wire/blockheader_test.go b/wire/blockheader_test.go new file mode 100644 index 00000000..c7ed3b80 --- /dev/null +++ b/wire/blockheader_test.go @@ -0,0 +1,231 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestBlockHeader tests the BlockHeader API. +func TestBlockHeader(t *testing.T) { + nonce64, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + nonce := uint32(nonce64) + + hash := mainNetGenesisHash + merkleHash := mainNetGenesisMerkleRoot + bits := uint32(0x1d00ffff) + bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce) + + // Ensure we get the same data back out. + if !bh.PrevBlock.IsEqual(&hash) { + t.Errorf("NewBlockHeader: wrong prev hash - got %v, want %v", + spew.Sprint(bh.PrevBlock), spew.Sprint(hash)) + } + if !bh.MerkleRoot.IsEqual(&merkleHash) { + t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v", + spew.Sprint(bh.MerkleRoot), spew.Sprint(merkleHash)) + } + if bh.Bits != bits { + t.Errorf("NewBlockHeader: wrong bits - got %v, want %v", + bh.Bits, bits) + } + if bh.Nonce != nonce { + t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v", + bh.Nonce, nonce) + } +} + +// TestBlockHeaderWire tests the BlockHeader wire encode and decode for various +// protocol versions. +func TestBlockHeaderWire(t *testing.T) { + nonce := uint32(123123) // 0x1e0f3 + + // baseBlockHdr is used in the various tests as a baseline BlockHeader. + bits := uint32(0x1d00ffff) + baseBlockHdr := &wire.BlockHeader{ + Version: 1, + PrevBlock: mainNetGenesisHash, + MerkleRoot: mainNetGenesisMerkleRoot, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Bits: bits, + Nonce: nonce, + } + + // baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr. + baseBlockHdrEncoded := []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 + } + + tests := []struct { + in *wire.BlockHeader // Data to encode + out *wire.BlockHeader // Expected decoded data + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteBlockHeader(&buf, test.pver, test.in) + if err != nil { + t.Errorf("writeBlockHeader #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the block header from wire format. + var bh wire.BlockHeader + rbuf := bytes.NewReader(test.buf) + err = wire.TstReadBlockHeader(rbuf, test.pver, &bh) + if err != nil { + t.Errorf("readBlockHeader #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&bh, test.out) { + t.Errorf("readBlockHeader #%d\n got: %s want: %s", i, + spew.Sdump(&bh), spew.Sdump(test.out)) + continue + } + } +} + +// TestBlockHeaderSerialize tests BlockHeader serialize and deserialize. +func TestBlockHeaderSerialize(t *testing.T) { + nonce := uint32(123123) // 0x1e0f3 + + // baseBlockHdr is used in the various tests as a baseline BlockHeader. + bits := uint32(0x1d00ffff) + baseBlockHdr := &wire.BlockHeader{ + Version: 1, + PrevBlock: mainNetGenesisHash, + MerkleRoot: mainNetGenesisMerkleRoot, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Bits: bits, + Nonce: nonce, + } + + // baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr. + baseBlockHdrEncoded := []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 + } + + tests := []struct { + in *wire.BlockHeader // Data to encode + out *wire.BlockHeader // Expected decoded data + buf []byte // Serialized data + }{ + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Serialize the block header. + var buf bytes.Buffer + err := test.in.Serialize(&buf) + if err != nil { + t.Errorf("Serialize #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("Serialize #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Deserialize the block header. + var bh wire.BlockHeader + rbuf := bytes.NewReader(test.buf) + err = bh.Deserialize(rbuf) + if err != nil { + t.Errorf("Deserialize #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&bh, test.out) { + t.Errorf("Deserialize #%d\n got: %s want: %s", i, + spew.Sdump(&bh), spew.Sdump(test.out)) + continue + } + } +} diff --git a/wire/common.go b/wire/common.go new file mode 100644 index 00000000..bf41d68e --- /dev/null +++ b/wire/common.go @@ -0,0 +1,531 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "math" + + "github.com/btcsuite/fastsha256" +) + +// Maximum payload size for a variable length integer. +const MaxVarIntPayload = 9 + +// readElement reads the next sequence of bytes from r using little endian +// depending on the concrete type of element pointed to. +func readElement(r io.Reader, element interface{}) error { + var scratch [8]byte + + // Attempt to read the element based on the concrete type via fast + // type assertions first. + switch e := element.(type) { + case *int32: + b := scratch[0:4] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = int32(binary.LittleEndian.Uint32(b)) + return nil + + case *uint32: + b := scratch[0:4] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = binary.LittleEndian.Uint32(b) + return nil + + case *int64: + b := scratch[0:8] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = int64(binary.LittleEndian.Uint64(b)) + return nil + + case *uint64: + b := scratch[0:8] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = binary.LittleEndian.Uint64(b) + return nil + + case *bool: + b := scratch[0:1] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + if b[0] == 0x00 { + *e = false + } else { + *e = true + } + return nil + + // Message header checksum. + case *[4]byte: + _, err := io.ReadFull(r, e[:]) + if err != nil { + return err + } + return nil + + // Message header command. + case *[CommandSize]uint8: + _, err := io.ReadFull(r, e[:]) + if err != nil { + return err + } + return nil + + // IP address. + case *[16]byte: + _, err := io.ReadFull(r, e[:]) + if err != nil { + return err + } + return nil + + case *ShaHash: + _, err := io.ReadFull(r, e[:]) + if err != nil { + return err + } + return nil + + case *ServiceFlag: + b := scratch[0:8] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = ServiceFlag(binary.LittleEndian.Uint64(b)) + return nil + + case *InvType: + b := scratch[0:4] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = InvType(binary.LittleEndian.Uint32(b)) + return nil + + case *BitcoinNet: + b := scratch[0:4] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = BitcoinNet(binary.LittleEndian.Uint32(b)) + return nil + + case *BloomUpdateType: + b := scratch[0:1] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = BloomUpdateType(b[0]) + return nil + + case *RejectCode: + b := scratch[0:1] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = RejectCode(b[0]) + return nil + } + + // Fall back to the slower binary.Read if a fast path was not available + // above. + return binary.Read(r, binary.LittleEndian, element) +} + +// readElements reads multiple items from r. It is equivalent to multiple +// calls to readElement. +func readElements(r io.Reader, elements ...interface{}) error { + for _, element := range elements { + err := readElement(r, element) + if err != nil { + return err + } + } + return nil +} + +// writeElement writes the little endian representation of element to w. +func writeElement(w io.Writer, element interface{}) error { + var scratch [8]byte + + // Attempt to write the element based on the concrete type via fast + // type assertions first. + switch e := element.(type) { + case int32: + b := scratch[0:4] + binary.LittleEndian.PutUint32(b, uint32(e)) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case uint32: + b := scratch[0:4] + binary.LittleEndian.PutUint32(b, e) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case int64: + b := scratch[0:8] + binary.LittleEndian.PutUint64(b, uint64(e)) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case uint64: + b := scratch[0:8] + binary.LittleEndian.PutUint64(b, e) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case bool: + b := scratch[0:1] + if e == true { + b[0] = 0x01 + } else { + b[0] = 0x00 + } + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + // Message header checksum. + case [4]byte: + _, err := w.Write(e[:]) + if err != nil { + return err + } + return nil + + // Message header command. + case [CommandSize]uint8: + _, err := w.Write(e[:]) + if err != nil { + return err + } + return nil + + // IP address. + case [16]byte: + _, err := w.Write(e[:]) + if err != nil { + return err + } + return nil + + case *ShaHash: + _, err := w.Write(e[:]) + if err != nil { + return err + } + return nil + + case ServiceFlag: + b := scratch[0:8] + binary.LittleEndian.PutUint64(b, uint64(e)) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case InvType: + b := scratch[0:4] + binary.LittleEndian.PutUint32(b, uint32(e)) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case BitcoinNet: + b := scratch[0:4] + binary.LittleEndian.PutUint32(b, uint32(e)) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case BloomUpdateType: + b := scratch[0:1] + b[0] = uint8(e) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + + case RejectCode: + b := scratch[0:1] + b[0] = uint8(e) + _, err := w.Write(b) + if err != nil { + return err + } + return nil + } + + // Fall back to the slower binary.Write if a fast path was not available + // above. + return binary.Write(w, binary.LittleEndian, element) +} + +// writeElements writes multiple items to w. It is equivalent to multiple +// calls to writeElement. +func writeElements(w io.Writer, elements ...interface{}) error { + for _, element := range elements { + err := writeElement(w, element) + if err != nil { + return err + } + } + return nil +} + +// readVarInt reads a variable length integer from r and returns it as a uint64. +func readVarInt(r io.Reader, pver uint32) (uint64, error) { + var b [8]byte + _, err := io.ReadFull(r, b[0:1]) + if err != nil { + return 0, err + } + + var rv uint64 + discriminant := uint8(b[0]) + switch discriminant { + case 0xff: + _, err := io.ReadFull(r, b[:]) + if err != nil { + return 0, err + } + rv = binary.LittleEndian.Uint64(b[:]) + + case 0xfe: + _, err := io.ReadFull(r, b[0:4]) + if err != nil { + return 0, err + } + rv = uint64(binary.LittleEndian.Uint32(b[:])) + + case 0xfd: + _, err := io.ReadFull(r, b[0:2]) + if err != nil { + return 0, err + } + rv = uint64(binary.LittleEndian.Uint16(b[:])) + + default: + rv = uint64(discriminant) + } + + return rv, nil +} + +// 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 { + _, err := w.Write([]byte{uint8(val)}) + return err + } + + if val <= math.MaxUint16 { + var buf [3]byte + buf[0] = 0xfd + binary.LittleEndian.PutUint16(buf[1:], uint16(val)) + _, err := w.Write(buf[:]) + return err + } + + if val <= math.MaxUint32 { + var buf [5]byte + buf[0] = 0xfe + binary.LittleEndian.PutUint32(buf[1:], uint32(val)) + _, err := w.Write(buf[:]) + return err + } + + var buf [9]byte + buf[0] = 0xff + binary.LittleEndian.PutUint64(buf[1:], val) + _, err := w.Write(buf[:]) + return err +} + +// VarIntSerializeSize returns the number of bytes it would take to serialize +// val as a variable length integer. +func VarIntSerializeSize(val uint64) int { + // The value is small enough to be represented by itself, so it's + // just 1 byte. + if val < 0xfd { + return 1 + } + + // Discriminant 1 byte plus 2 bytes for the uint16. + if val <= math.MaxUint16 { + return 3 + } + + // Discriminant 1 byte plus 4 bytes for the uint32. + if val <= math.MaxUint32 { + return 5 + } + + // Discriminant 1 byte plus 8 bytes for the uint64. + return 9 +} + +// readVarString reads a variable length string from r and returns it as a Go +// string. A varString is encoded as a varInt containing the length of the +// string, and the bytes that represent the string itself. An error is returned +// if the length is greater than the maximum block payload size, since it would +// not be possible to put a varString of that size into a block anyways and it +// also 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) + if err != nil { + return "", err + } + + // Prevent variable length strings that are larger than the maximum + // message size. It would be possible to cause memory exhaustion and + // panics without a sane upper bound on this count. + if count > MaxMessagePayload { + str := fmt.Sprintf("variable length string is too long "+ + "[count %d, max %d]", count, MaxMessagePayload) + return "", messageError("readVarString", str) + } + + buf := make([]byte, count) + _, err = io.ReadFull(r, buf) + if err != nil { + return "", err + } + return string(buf), nil +} + +// writeVarString serializes str to w as a varInt 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))) + if err != nil { + return err + } + _, err = w.Write([]byte(str)) + if err != nil { + return err + } + return nil +} + +// readVarBytes 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 exhuastion +// attacks and forced panics thorugh malformed messages. The fieldName +// parameter is only used for the error message so it provides more context in +// the error. +func readVarBytes(r io.Reader, pver uint32, maxAllowed uint32, + fieldName string) ([]byte, error) { + + count, err := readVarInt(r, pver) + if err != nil { + return nil, err + } + + // Prevent byte array larger than the max message size. It would + // be possible to cause memory exhaustion and panics without a sane + // upper bound on this count. + if count > uint64(maxAllowed) { + str := fmt.Sprintf("%s is larger than the max allowed size "+ + "[count %d, max %d]", fieldName, count, maxAllowed) + return nil, messageError("readVarBytes", str) + } + + b := make([]byte, count) + _, err = io.ReadFull(r, b) + if err != nil { + return nil, err + } + return b, nil +} + +// writeVarInt 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 { + slen := uint64(len(bytes)) + err := writeVarInt(w, pver, slen) + if err != nil { + return err + } + + _, err = w.Write(bytes) + if err != nil { + return err + } + return nil +} + +// randomUint64 returns a cryptographically random uint64 value. This +// unexported version takes a reader primarily to ensure the error paths +// can be properly tested by passing a fake reader in the tests. +func randomUint64(r io.Reader) (uint64, error) { + var b [8]byte + _, err := io.ReadFull(r, b[:]) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint64(b[:]), nil +} + +// RandomUint64 returns a cryptographically random uint64 value. +func RandomUint64() (uint64, error) { + return randomUint64(rand.Reader) +} + +// DoubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. +func DoubleSha256(b []byte) []byte { + hasher := fastsha256.New() + hasher.Write(b) + sum := hasher.Sum(nil) + hasher.Reset() + hasher.Write(sum) + return hasher.Sum(nil) +} diff --git a/wire/common_test.go b/wire/common_test.go new file mode 100644 index 00000000..9bc810d6 --- /dev/null +++ b/wire/common_test.go @@ -0,0 +1,703 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strings" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// mainNetGenesisHash is the hash of the first block in the block chain for the +// main network (genesis block). +var mainNetGenesisHash = wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 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, +}) + +// mainNetGenesisMerkleRoot is the hash of the first transaction in the genesis +// block for the main network. +var mainNetGenesisMerkleRoot = wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 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, +}) + +// fakeRandReader implements the io.Reader interface and is used to force +// errors in the RandomUint64 function. +type fakeRandReader struct { + n int + err error +} + +// Read returns the fake reader error and the lesser of the fake reader value +// and the length of p. +func (r *fakeRandReader) Read(p []byte) (int, error) { + n := r.n + if n > len(p) { + n = len(p) + } + return n, r.err +} + +// TestElementWire tests wire encode and decode for various element types. This +// is mainly to test the "fast" paths in readElement and writeElement which use +// type assertions to avoid reflection when possible. +func TestElementWire(t *testing.T) { + type writeElementReflect int32 + + tests := []struct { + in interface{} // Value to encode + buf []byte // Wire encoding + }{ + {int32(1), []byte{0x01, 0x00, 0x00, 0x00}}, + {uint32(256), []byte{0x00, 0x01, 0x00, 0x00}}, + { + int64(65536), + []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + uint64(4294967296), + []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, + }, + { + true, + []byte{0x01}, + }, + { + false, + []byte{0x00}, + }, + { + [4]byte{0x01, 0x02, 0x03, 0x04}, + []byte{0x01, 0x02, 0x03, 0x04}, + }, + { + [wire.CommandSize]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, + }, + []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, + }, + }, + { + [16]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + }, + []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + }, + }, + { + (*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy. + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }), + []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }, + }, + { + wire.ServiceFlag(wire.SFNodeNetwork), + []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + wire.InvType(wire.InvTypeTx), + []byte{0x01, 0x00, 0x00, 0x00}, + }, + { + wire.BitcoinNet(wire.MainNet), + []byte{0xf9, 0xbe, 0xb4, 0xd9}, + }, + // Type not supported by the "fast" path and requires reflection. + { + writeElementReflect(1), + []byte{0x01, 0x00, 0x00, 0x00}, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Write to wire format. + var buf bytes.Buffer + err := wire.TstWriteElement(&buf, test.in) + if err != nil { + t.Errorf("writeElement #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeElement #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Read from wire format. + rbuf := bytes.NewReader(test.buf) + val := test.in + if reflect.ValueOf(test.in).Kind() != reflect.Ptr { + val = reflect.New(reflect.TypeOf(test.in)).Interface() + } + err = wire.TstReadElement(rbuf, val) + if err != nil { + t.Errorf("readElement #%d error %v", i, err) + continue + } + ival := val + if reflect.ValueOf(test.in).Kind() != reflect.Ptr { + ival = reflect.Indirect(reflect.ValueOf(val)).Interface() + } + if !reflect.DeepEqual(ival, test.in) { + t.Errorf("readElement #%d\n got: %s want: %s", i, + spew.Sdump(ival), spew.Sdump(test.in)) + continue + } + } +} + +// TestElementWireErrors performs negative tests against wire encode and decode +// of various element types to confirm error paths work correctly. +func TestElementWireErrors(t *testing.T) { + tests := []struct { + in interface{} // Value to encode + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + {int32(1), 0, io.ErrShortWrite, io.EOF}, + {uint32(256), 0, io.ErrShortWrite, io.EOF}, + {int64(65536), 0, io.ErrShortWrite, io.EOF}, + {true, 0, io.ErrShortWrite, io.EOF}, + {[4]byte{0x01, 0x02, 0x03, 0x04}, 0, io.ErrShortWrite, io.EOF}, + { + [wire.CommandSize]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, + }, + 0, io.ErrShortWrite, io.EOF, + }, + { + [16]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + }, + 0, io.ErrShortWrite, io.EOF, + }, + { + (*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy. + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }), + 0, io.ErrShortWrite, io.EOF, + }, + {wire.ServiceFlag(wire.SFNodeNetwork), 0, io.ErrShortWrite, io.EOF}, + {wire.InvType(wire.InvTypeTx), 0, io.ErrShortWrite, io.EOF}, + {wire.BitcoinNet(wire.MainNet), 0, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := wire.TstWriteElement(w, test.in) + if err != test.writeErr { + t.Errorf("writeElement #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + r := newFixedReader(test.max, nil) + val := test.in + if reflect.ValueOf(test.in).Kind() != reflect.Ptr { + val = reflect.New(reflect.TypeOf(test.in)).Interface() + } + err = wire.TstReadElement(r, val) + if err != test.readErr { + t.Errorf("readElement #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestVarIntWire tests wire encode and decode for variable length integers. +func TestVarIntWire(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + in uint64 // Value to encode + out uint64 // Expected decoded value + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + // Single byte + {0, 0, []byte{0x00}, pver}, + // Max single byte + {0xfc, 0xfc, []byte{0xfc}, pver}, + // Min 2-byte + {0xfd, 0xfd, []byte{0xfd, 0x0fd, 0x00}, pver}, + // Max 2-byte + {0xffff, 0xffff, []byte{0xfd, 0xff, 0xff}, pver}, + // Min 4-byte + {0x10000, 0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}, pver}, + // Max 4-byte + {0xffffffff, 0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, pver}, + // Min 8-byte + { + 0x100000000, 0x100000000, + []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, + pver, + }, + // Max 8-byte + { + 0xffffffffffffffff, 0xffffffffffffffff, + []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + pver, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteVarInt(&buf, test.pver, test.in) + if err != nil { + t.Errorf("writeVarInt #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeVarInt #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode from wire format. + rbuf := bytes.NewReader(test.buf) + val, err := wire.TstReadVarInt(rbuf, test.pver) + if err != nil { + t.Errorf("readVarInt #%d error %v", i, err) + continue + } + if val != test.out { + t.Errorf("readVarInt #%d\n got: %d want: %d", i, + val, test.out) + continue + } + } +} + +// TestVarIntWireErrors performs negative tests against wire encode and decode +// of variable length integers to confirm error paths work correctly. +func TestVarIntWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + in uint64 // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force errors on discriminant. + {0, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF}, + // Force errors on 2-byte read/write. + {0xfd, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force errors on 4-byte read/write. + {0x10000, []byte{0xfe}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force errors on 8-byte read/write. + {0x100000000, []byte{0xff}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := wire.TstWriteVarInt(w, test.pver, test.in) + if err != test.writeErr { + t.Errorf("writeVarInt #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + r := newFixedReader(test.max, test.buf) + _, err = wire.TstReadVarInt(r, test.pver) + if err != test.readErr { + t.Errorf("readVarInt #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestVarIntWire tests the serialize size for variable length integers. +func TestVarIntSerializeSize(t *testing.T) { + tests := []struct { + val uint64 // Value to get the serialized size for + size int // Expected serialized size + }{ + // Single byte + {0, 1}, + // Max single byte + {0xfc, 1}, + // Min 2-byte + {0xfd, 3}, + // Max 2-byte + {0xffff, 3}, + // Min 4-byte + {0x10000, 5}, + // Max 4-byte + {0xffffffff, 5}, + // Min 8-byte + {0x100000000, 9}, + // Max 8-byte + {0xffffffffffffffff, 9}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + serializedSize := wire.VarIntSerializeSize(test.val) + if serializedSize != test.size { + t.Errorf("VarIntSerializeSize #%d got: %d, want: %d", i, + serializedSize, test.size) + continue + } + } +} + +// TestVarStringWire tests wire encode and decode for variable length strings. +func TestVarStringWire(t *testing.T) { + pver := wire.ProtocolVersion + + // str256 is a string that takes a 2-byte varint to encode. + str256 := strings.Repeat("test", 64) + + tests := []struct { + in string // String to encode + out string // String to decoded value + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + // Empty string + {"", "", []byte{0x00}, pver}, + // Single byte varint + string + {"Test", "Test", append([]byte{0x04}, []byte("Test")...), pver}, + // 2-byte varint + string + {str256, str256, append([]byte{0xfd, 0x00, 0x01}, []byte(str256)...), pver}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteVarString(&buf, test.pver, test.in) + if err != nil { + t.Errorf("writeVarString #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeVarString #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode from wire format. + rbuf := bytes.NewReader(test.buf) + val, err := wire.TstReadVarString(rbuf, test.pver) + if err != nil { + t.Errorf("readVarString #%d error %v", i, err) + continue + } + if val != test.out { + t.Errorf("readVarString #%d\n got: %s want: %s", i, + val, test.out) + continue + } + } +} + +// TestVarStringWireErrors performs negative tests against wire encode and +// decode of variable length strings to confirm error paths work correctly. +func TestVarStringWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + + // str256 is a string that takes a 2-byte varint to encode. + str256 := strings.Repeat("test", 64) + + tests := []struct { + in string // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force errors on empty string. + {"", []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error on single byte varint + string. + {"Test", []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force errors on 2-byte varint + string. + {str256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := wire.TstWriteVarString(w, test.pver, test.in) + if err != test.writeErr { + t.Errorf("writeVarString #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + r := newFixedReader(test.max, test.buf) + _, err = wire.TstReadVarString(r, test.pver) + if err != test.readErr { + t.Errorf("readVarString #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestVarStringOverflowErrors performs tests to ensure deserializing variable +// length strings intentionally crafted to use large values for the string +// length are handled properly. This could otherwise potentially be used as an +// attack vector. +func TestVarStringOverflowErrors(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + err error // Expected error + }{ + {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + pver, &wire.MessageError{}}, + {[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + pver, &wire.MessageError{}}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + rbuf := bytes.NewReader(test.buf) + _, err := wire.TstReadVarString(rbuf, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("readVarString #%d wrong error got: %v, "+ + "want: %v", i, err, reflect.TypeOf(test.err)) + continue + } + } + +} + +// TestVarBytesWire tests wire encode and decode for variable length byte array. +func TestVarBytesWire(t *testing.T) { + pver := wire.ProtocolVersion + + // bytes256 is a byte array that takes a 2-byte varint to encode. + bytes256 := bytes.Repeat([]byte{0x01}, 256) + + tests := []struct { + in []byte // Byte Array to write + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + // Empty byte array + {[]byte{}, []byte{0x00}, pver}, + // Single byte varint + byte array + {[]byte{0x01}, []byte{0x01, 0x01}, pver}, + // 2-byte varint + byte array + {bytes256, append([]byte{0xfd, 0x00, 0x01}, bytes256...), pver}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteVarBytes(&buf, test.pver, test.in) + if err != nil { + t.Errorf("writeVarBytes #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeVarBytes #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode from wire format. + rbuf := bytes.NewReader(test.buf) + val, err := wire.TstReadVarBytes(rbuf, test.pver, + wire.MaxMessagePayload, "test payload") + if err != nil { + t.Errorf("readVarBytes #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("readVarBytes #%d\n got: %s want: %s", i, + val, test.buf) + continue + } + } +} + +// TestVarBytesWireErrors performs negative tests against wire encode and +// decode of variable length byte arrays to confirm error paths work correctly. +func TestVarBytesWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + + // bytes256 is a byte array that takes a 2-byte varint to encode. + bytes256 := bytes.Repeat([]byte{0x01}, 256) + + tests := []struct { + in []byte // Byte Array to write + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force errors on empty byte array. + {[]byte{}, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error on single byte varint + byte array. + {[]byte{0x01, 0x02, 0x03}, []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force errors on 2-byte varint + byte array. + {bytes256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := wire.TstWriteVarBytes(w, test.pver, test.in) + if err != test.writeErr { + t.Errorf("writeVarBytes #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + r := newFixedReader(test.max, test.buf) + _, err = wire.TstReadVarBytes(r, test.pver, + wire.MaxMessagePayload, "test payload") + if err != test.readErr { + t.Errorf("readVarBytes #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestVarBytesOverflowErrors performs tests to ensure deserializing variable +// length byte arrays intentionally crafted to use large values for the array +// length are handled properly. This could otherwise potentially be used as an +// attack vector. +func TestVarBytesOverflowErrors(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + err error // Expected error + }{ + {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + pver, &wire.MessageError{}}, + {[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + pver, &wire.MessageError{}}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + rbuf := bytes.NewReader(test.buf) + _, err := wire.TstReadVarBytes(rbuf, test.pver, + wire.MaxMessagePayload, "test payload") + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("readVarBytes #%d wrong error got: %v, "+ + "want: %v", i, err, reflect.TypeOf(test.err)) + continue + } + } + +} + +// TestRandomUint64 exercises the randomness of the random number generator on +// the system by ensuring the probability of the generated numbers. If the RNG +// is evenly distributed as a proper cryptographic RNG should be, there really +// should only be 1 number < 2^56 in 2^8 tries for a 64-bit number. However, +// use a higher number of 5 to really ensure the test doesn't fail unless the +// RNG is just horrendous. +func TestRandomUint64(t *testing.T) { + tries := 1 << 8 // 2^8 + watermark := uint64(1 << 56) // 2^56 + maxHits := 5 + badRNG := "The random number generator on this system is clearly " + + "terrible since we got %d values less than %d in %d runs " + + "when only %d was expected" + + numHits := 0 + for i := 0; i < tries; i++ { + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64 iteration %d failed - err %v", + i, err) + return + } + if nonce < watermark { + numHits++ + } + if numHits > maxHits { + str := fmt.Sprintf(badRNG, numHits, watermark, tries, maxHits) + t.Errorf("Random Uint64 iteration %d failed - %v %v", i, + str, numHits) + return + } + } +} + +// TestRandomUint64Errors uses a fake reader to force error paths to be executed +// and checks the results accordingly. +func TestRandomUint64Errors(t *testing.T) { + // Test short reads. + fr := &fakeRandReader{n: 2, err: io.EOF} + nonce, err := wire.TstRandomUint64(fr) + if err != io.ErrUnexpectedEOF { + t.Errorf("TestRandomUint64Fails: Error not expected value of %v [%v]", + io.ErrShortBuffer, err) + } + if nonce != 0 { + t.Errorf("TestRandomUint64Fails: nonce is not 0 [%v]", nonce) + } +} diff --git a/wire/doc.go b/wire/doc.go new file mode 100644 index 00000000..2e20ad78 --- /dev/null +++ b/wire/doc.go @@ -0,0 +1,159 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package wire implements the bitcoin wire protocol. + +For the complete details of the bitcoin protocol, see the official wiki entry +at https://en.bitcoin.it/wiki/Protocol_specification. The following only serves +as a quick overview to provide information on how to use the package. + +At a high level, this package provides support for marshalling and unmarshalling +supported bitcoin messages to and from the wire. This package does not deal +with the specifics of message handling such as what to do when a message is +received. This provides the caller with a high level of flexibility. + +Bitcoin Message Overview + +The bitcoin protocol consists of exchanging messages between peers. Each +message is preceded by a header which identifies information about it such as +which bitcoin network it is a part of, its type, how big it is, and a checksum +to verify validity. All encoding and decoding of message headers is handled by +this package. + +To accomplish this, there is a generic interface for bitcoin messages named +Message which allows messages of any type to be read, written, or passed around +through channels, functions, etc. In addition, concrete implementations of most +of the currently supported bitcoin messages are provided. For these supported +messages, all of the details of marshalling and unmarshalling to and from the +wire using bitcoin encoding are handled so the caller doesn't have to concern +themselves with the specifics. + +Message Interaction + +The following provides a quick summary of how the bitcoin messages are intended +to interact with one another. As stated above, these interactions are not +directly handled by this package. For more in-depth details about the +appropriate interactions, see the official bitcoin protocol wiki entry at +https://en.bitcoin.it/wiki/Protocol_specification. + +The initial handshake consists of two peers sending each other a version message +(MsgVersion) followed by responding with a verack message (MsgVerAck). Both +peers use the information in the version message (MsgVersion) to negotiate +things such as protocol version and supported services with each other. Once +the initial handshake is complete, the following chart indicates message +interactions in no particular order. + + Peer A Sends Peer B Responds + ---------------------------------------------------------------------------- + getaddr message (MsgGetAddr) addr message (MsgAddr) + getblocks message (MsgGetBlocks) inv message (MsgInv) + inv message (MsgInv) getdata message (MsgGetData) + getdata message (MsgGetData) block message (MsgBlock) -or- + tx message (MsgTx) -or- + notfound message (MsgNotFound) + getheaders message (MsgGetHeaders) headers message (MsgHeaders) + ping message (MsgPing) pong message (MsgHeaders)* -or- + (none -- Ability to send message is enough) + + NOTES: + * The pong message was not added until later protocol versions as defined + in BIP0031. The BIP0031Version constant can be used to detect a recent + enough protocol version for this purpose (version > BIP0031Version). + +Common Parameters + +There are several common parameters that arise when using this package to read +and write bitcoin messages. The following sections provide a quick overview of +these parameters so the next sections can build on them. + +Protocol Version + +The protocol version should be negotiated with the remote peer at a higher +level than this package via the version (MsgVersion) message exchange, however, +this package provides the wire.ProtocolVersion constant which indicates the +latest protocol version this package supports and is typically the value to use +for all outbound connections before a potentially lower protocol version is +negotiated. + +Bitcoin Network + +The bitcoin network is a magic number which is used to identify the start of a +message and which bitcoin network the message applies to. This package provides +the following constants: + + wire.MainNet + wire.TestNet (Regression test network) + wire.TestNet3 (Test network version 3) + wire.SimNet (Simulation test network) + +Determining Message Type + +As discussed in the bitcoin message overview section, this package reads +and writes bitcoin messages using a generic interface named Message. In +order to determine the actual concrete type of the message, use a type +switch or type assertion. An example of a type switch follows: + + // Assumes msg is already a valid concrete message such as one created + // via NewMsgVersion or read via ReadMessage. + switch msg := msg.(type) { + case *wire.MsgVersion: + // The message is a pointer to a MsgVersion struct. + fmt.Printf("Protocol version: %v", msg.ProtocolVersion) + case *wire.MsgBlock: + // The message is a pointer to a MsgBlock struct. + fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount) + } + +Reading Messages + +In order to unmarshall bitcoin messages from the wire, use the ReadMessage +function. It accepts any io.Reader, but typically this will be a net.Conn to +a remote node running a bitcoin peer. Example syntax is: + + // Reads and validates the next bitcoin message from conn using the + // protocol version pver and the bitcoin network btcnet. The returns + // are a wire.Message, a []byte which contains the unmarshalled + // raw payload, and a possible error. + msg, rawPayload, err := wire.ReadMessage(conn, pver, btcnet) + if err != nil { + // Log and handle the error + } + +Writing Messages + +In order to marshall bitcoin messages to the wire, use the WriteMessage +function. It accepts any io.Writer, but typically this will be a net.Conn to +a remote node running a bitcoin peer. Example syntax to request addresses +from a remote peer is: + + // Create a new getaddr bitcoin message. + msg := wire.NewMsgGetAddr() + + // Writes a bitcoin message msg to conn using the protocol version + // pver, and the bitcoin network btcnet. The return is a possible + // error. + err := wire.WriteMessage(conn, msg, pver, btcnet) + if err != nil { + // Log and handle the error + } + +Errors + +Errors returned by this package are either the raw errors provided by underlying +calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and +io.ErrShortWrite, or of type wire.MessageError. This allows the caller to +differentiate between general IO errors and malformed messages through type +assertions. + +Bitcoin Improvement Proposals + +This package includes spec changes outlined by the following BIPs: + + BIP0014 (https://en.bitcoin.it/wiki/BIP_0014) + BIP0031 (https://en.bitcoin.it/wiki/BIP_0031) + BIP0035 (https://en.bitcoin.it/wiki/BIP_0035) + BIP0037 (https://en.bitcoin.it/wiki/BIP_0037) +*/ +package wire diff --git a/wire/error.go b/wire/error.go new file mode 100644 index 00000000..99a194a4 --- /dev/null +++ b/wire/error.go @@ -0,0 +1,34 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" +) + +// MessageError describes an issue with a message. +// An example of some potential issues are messages from the wrong bitcoin +// network, invalid commands, mismatched checksums, and exceeding max payloads. +// +// This provides a mechanism for the caller to type assert the error to +// differentiate between general io errors such as io.EOF and issues that +// resulted from malformed messages. +type MessageError struct { + Func string // Function name + Description string // Human readable description of the issue +} + +// Error satisfies the error interface and prints human-readable errors. +func (e *MessageError) Error() string { + if e.Func != "" { + return fmt.Sprintf("%v: %v", e.Func, e.Description) + } + return e.Description +} + +// messageError creates an error for the given function and description. +func messageError(f string, desc string) *MessageError { + return &MessageError{Func: f, Description: desc} +} diff --git a/wire/fakeconn_test.go b/wire/fakeconn_test.go new file mode 100644 index 00000000..a28b2996 --- /dev/null +++ b/wire/fakeconn_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "net" + "time" +) + +// fakeConn implements the net.Conn interface and is used to test functions +// which work with a net.Conn without having to actually make any real +// connections. +type fakeConn struct { + localAddr net.Addr + remoteAddr net.Addr +} + +// Read doesn't do anything. It just satisfies the net.Conn interface. +func (c *fakeConn) Read(b []byte) (n int, err error) { + return 0, nil +} + +// Write doesn't do anything. It just satisfies the net.Conn interface. +func (c *fakeConn) Write(b []byte) (n int, err error) { + return 0, nil +} + +// Close doesn't do anything. It just satisfies the net.Conn interface. +func (c *fakeConn) Close() error { + return nil +} + +// LocalAddr returns the localAddr field of the fake connection and satisfies +// the net.Conn interface. +func (c *fakeConn) LocalAddr() net.Addr { + return c.localAddr +} + +// RemoteAddr returns the remoteAddr field of the fake connection and satisfies +// the net.Conn interface. +func (c *fakeConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +// SetDeadline doesn't do anything. It just satisfies the net.Conn interface. +func (c *fakeConn) SetDeadline(t time.Time) error { + return nil +} + +// SetReadDeadline doesn't do anything. It just satisfies the net.Conn +// interface. +func (c *fakeConn) SetReadDeadline(t time.Time) error { + return nil +} + +// SetWriteDeadline doesn't do anything. It just satisfies the net.Conn +// interface. +func (c *fakeConn) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/wire/fakemessage_test.go b/wire/fakemessage_test.go new file mode 100644 index 00000000..f64e4f83 --- /dev/null +++ b/wire/fakemessage_test.go @@ -0,0 +1,60 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "io" + + "github.com/btcsuite/btcd/wire" +) + +// fakeMessage implements the wire.Message interface and is used to force +// encode errors in messages. +type fakeMessage struct { + command string + payload []byte + forceEncodeErr bool + forceLenErr bool +} + +// BtcDecode doesn't do anything. It just satisfies the wire.Message +// interface. +func (msg *fakeMessage) BtcDecode(r io.Reader, pver uint32) error { + return nil +} + +// BtcEncode writes the payload field of the fake message or forces an error +// if the forceEncodeErr flag of the fake message is set. It also satisfies the +// wire.Message interface. +func (msg *fakeMessage) BtcEncode(w io.Writer, pver uint32) error { + if msg.forceEncodeErr { + err := &wire.MessageError{ + Func: "fakeMessage.BtcEncode", + Description: "intentional error", + } + return err + } + + _, err := w.Write(msg.payload) + return err +} + +// Command returns the command field of the fake message and satisfies the +// wire.Message interface. +func (msg *fakeMessage) Command() string { + return msg.command +} + +// MaxPayloadLength returns the length of the payload field of fake message +// or a smaller value if the forceLenErr flag of the fake message is set. It +// satisfies the wire.Message interface. +func (msg *fakeMessage) MaxPayloadLength(pver uint32) uint32 { + lenp := uint32(len(msg.payload)) + if msg.forceLenErr { + return lenp - 1 + } + + return lenp +} diff --git a/wire/fixedIO_test.go b/wire/fixedIO_test.go new file mode 100644 index 00000000..806790a2 --- /dev/null +++ b/wire/fixedIO_test.go @@ -0,0 +1,77 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" +) + +// fixedWriter implements the io.Writer interface and intentially allows +// testing of error paths by forcing short writes. +type fixedWriter struct { + b []byte + pos int +} + +// Write writes the contents of p to w. When the contents of p would cause +// the writer to exceed the maximum allowed size of the fixed writer, +// io.ErrShortWrite is returned and the writer is left unchanged. +// +// This satisfies the io.Writer interface. +func (w *fixedWriter) Write(p []byte) (n int, err error) { + lenp := len(p) + if w.pos+lenp > cap(w.b) { + return 0, io.ErrShortWrite + } + n = lenp + w.pos += copy(w.b[w.pos:], p) + return +} + +// Bytes returns the bytes alreayd written to the fixed writer. +func (w *fixedWriter) Bytes() []byte { + return w.b +} + +// newFixedWriter returns a new io.Writer that will error once more bytes than +// the specified max have been written. +func newFixedWriter(max int) io.Writer { + b := make([]byte, max, max) + fw := fixedWriter{b, 0} + return &fw +} + +// fixedReader implements the io.Reader interface and intentially allows +// testing of error paths by forcing short reads. +type fixedReader struct { + buf []byte + pos int + iobuf *bytes.Buffer +} + +// Read reads the next len(p) bytes from the fixed reader. When the number of +// bytes read would exceed the maximum number of allowed bytes to be read from +// the fixed writer, an error is returned. +// +// This satisfies the io.Reader interface. +func (fr *fixedReader) Read(p []byte) (n int, err error) { + n, err = fr.iobuf.Read(p) + fr.pos += n + return +} + +// newFixedReader returns a new io.Reader that will error once more bytes than +// the specified max have been read. +func newFixedReader(max int, buf []byte) io.Reader { + b := make([]byte, max, max) + if buf != nil { + copy(b[:], buf) + } + + iobuf := bytes.NewBuffer(b) + fr := fixedReader{b, 0, iobuf} + return &fr +} diff --git a/wire/internal_test.go b/wire/internal_test.go new file mode 100644 index 00000000..9b7b0e3c --- /dev/null +++ b/wire/internal_test.go @@ -0,0 +1,130 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +This test file is part of the wire package rather than than the wire_test +package so it can bridge access to the internals to properly test cases which +are either not possible or can't reliably be tested via the public interface. +The functions are only exported while the tests are being run. +*/ + +package wire + +import ( + "io" +) + +const ( + // MaxTxPerBlock makes the internal maxTxPerBlock constant available to + // the test package. + MaxTxPerBlock = maxTxPerBlock + + // MaxFlagsPerMerkleBlock makes the internal maxFlagsPerMerkleBlock + // constant available to the test package. + MaxFlagsPerMerkleBlock = maxFlagsPerMerkleBlock + + // MaxCountSetCancel makes the internal maxCountSetCancel constant + // available to the test package. + MaxCountSetCancel = maxCountSetCancel + + // MaxCountSetSubVer makes the internal maxCountSetSubVer constant + // available to the test package. + MaxCountSetSubVer = maxCountSetSubVer +) + +// TstRandomUint64 makes the internal randomUint64 function available to the +// test package. +func TstRandomUint64(r io.Reader) (uint64, error) { + return randomUint64(r) +} + +// TstReadElement makes the internal readElement function available to the +// test package. +func TstReadElement(r io.Reader, element interface{}) error { + return readElement(r, element) +} + +// TstWriteElement makes the internal writeElement function available to the +// test package. +func TstWriteElement(w io.Writer, element interface{}) error { + return writeElement(w, element) +} + +// TstReadVarInt makes the internal readVarInt function available to the +// test package. +func TstReadVarInt(r io.Reader, pver uint32) (uint64, error) { + return readVarInt(r, pver) +} + +// TstWriteVarInt makes the internal writeVarInt function available to the +// test package. +func TstWriteVarInt(w io.Writer, pver uint32, val uint64) error { + return writeVarInt(w, pver, val) +} + +// TstReadVarString makes the internal readVarString function available to the +// test package. +func TstReadVarString(r io.Reader, pver uint32) (string, error) { + return readVarString(r, pver) +} + +// TstWriteVarString makes the internal writeVarString function available to the +// test package. +func TstWriteVarString(w io.Writer, pver uint32, str string) error { + return writeVarString(w, pver, str) +} + +// TstReadVarBytes makes the internal readVarBytes function available to the +// test package. +func TstReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) { + return readVarBytes(r, pver, maxAllowed, fieldName) +} + +// TstWriteVarBytes makes the internal writeVarBytes function available to the +// test package. +func TstWriteVarBytes(w io.Writer, pver uint32, bytes []byte) error { + return writeVarBytes(w, pver, bytes) +} + +// TstReadNetAddress makes the internal readNetAddress function available to +// the test package. +func TstReadNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { + return readNetAddress(r, pver, na, ts) +} + +// TstWriteNetAddress makes the internal writeNetAddress function available to +// the test package. +func TstWriteNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error { + return writeNetAddress(w, pver, na, ts) +} + +// TstMaxNetAddressPayload makes the internal maxNetAddressPayload function +// available to the test package. +func TstMaxNetAddressPayload(pver uint32) uint32 { + return maxNetAddressPayload(pver) +} + +// TstReadInvVect makes the internal readInvVect function available to the test +// package. +func TstReadInvVect(r io.Reader, pver uint32, iv *InvVect) error { + return readInvVect(r, pver, iv) +} + +// TstWriteInvVect makes the internal writeInvVect function available to the +// test package. +func TstWriteInvVect(w io.Writer, pver uint32, iv *InvVect) error { + return writeInvVect(w, pver, iv) +} + +// TstReadBlockHeader makes the internal readBlockHeader function available to +// the test package. +func TstReadBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error { + return readBlockHeader(r, pver, bh) +} + +// TstWriteBlockHeader makes the internal writeBlockHeader function available to +// the test package. +func TstWriteBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error { + return writeBlockHeader(w, pver, bh) +} diff --git a/wire/invvect.go b/wire/invvect.go new file mode 100644 index 00000000..7df74dac --- /dev/null +++ b/wire/invvect.go @@ -0,0 +1,82 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +const ( + // MaxInvPerMsg is the maximum number of inventory vectors that can be in a + // single bitcoin inv message. + MaxInvPerMsg = 50000 + + // Maximum payload size for an inventory vector. + maxInvVectPayload = 4 + HashSize +) + +// InvType represents the allowed types of inventory vectors. See InvVect. +type InvType uint32 + +// These constants define the various supported inventory vector types. +const ( + InvTypeError InvType = 0 + InvTypeTx InvType = 1 + InvTypeBlock InvType = 2 + InvTypeFilteredBlock InvType = 3 +) + +// Map of service flags back to their constant names for pretty printing. +var ivStrings = map[InvType]string{ + InvTypeError: "ERROR", + InvTypeTx: "MSG_TX", + InvTypeBlock: "MSG_BLOCK", + InvTypeFilteredBlock: "MSG_FILTERED_BLOCK", +} + +// String returns the InvType in human-readable form. +func (invtype InvType) String() string { + if s, ok := ivStrings[invtype]; ok { + return s + } + + return fmt.Sprintf("Unknown InvType (%d)", uint32(invtype)) +} + +// InvVect defines a bitcoin inventory vector which is used to describe data, +// as specified by the Type field, that a peer wants, has, or does not have to +// another peer. +type InvVect struct { + Type InvType // Type of data + Hash ShaHash // Hash of the data +} + +// NewInvVect returns a new InvVect using the provided type and hash. +func NewInvVect(typ InvType, hash *ShaHash) *InvVect { + return &InvVect{ + Type: typ, + Hash: *hash, + } +} + +// readInvVect reads an encoded InvVect from r depending on the protocol +// version. +func readInvVect(r io.Reader, pver uint32, iv *InvVect) error { + err := readElements(r, &iv.Type, &iv.Hash) + if err != nil { + return err + } + return nil +} + +// writeInvVect serializes an InvVect to w depending on the protocol version. +func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error { + err := writeElements(w, iv.Type, &iv.Hash) + if err != nil { + return err + } + return nil +} diff --git a/wire/invvect_test.go b/wire/invvect_test.go new file mode 100644 index 00000000..3982390b --- /dev/null +++ b/wire/invvect_test.go @@ -0,0 +1,269 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestInvVectStringer tests the stringized output for inventory vector types. +func TestInvTypeStringer(t *testing.T) { + tests := []struct { + in wire.InvType + want string + }{ + {wire.InvTypeError, "ERROR"}, + {wire.InvTypeTx, "MSG_TX"}, + {wire.InvTypeBlock, "MSG_BLOCK"}, + {0xffffffff, "Unknown InvType (4294967295)"}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result := test.in.String() + if result != test.want { + t.Errorf("String #%d\n got: %s want: %s", i, result, + test.want) + continue + } + } + +} + +// TestInvVect tests the InvVect API. +func TestInvVect(t *testing.T) { + ivType := wire.InvTypeBlock + hash := wire.ShaHash{} + + // Ensure we get the same payload and signature back out. + iv := wire.NewInvVect(ivType, &hash) + if iv.Type != ivType { + t.Errorf("NewInvVect: wrong type - got %v, want %v", + iv.Type, ivType) + } + if !iv.Hash.IsEqual(&hash) { + t.Errorf("NewInvVect: wrong hash - got %v, want %v", + spew.Sdump(iv.Hash), spew.Sdump(hash)) + } + +} + +// TestInvVectWire tests the InvVect wire encode and decode for various +// protocol versions and supported inventory vector types. +func TestInvVectWire(t *testing.T) { + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + baseHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // errInvVect is an inventory vector with an error. + errInvVect := wire.InvVect{ + Type: wire.InvTypeError, + Hash: wire.ShaHash{}, + } + + // errInvVectEncoded is the wire encoded bytes of errInvVect. + errInvVectEncoded := []byte{ + 0x00, 0x00, 0x00, 0x00, // InvTypeError + 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, // No hash + } + + // txInvVect is an inventory vector representing a transaction. + txInvVect := wire.InvVect{ + Type: wire.InvTypeTx, + Hash: *baseHash, + } + + // txInvVectEncoded is the wire encoded bytes of txInvVect. + txInvVectEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, // InvTypeTx + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + } + + // blockInvVect is an inventory vector representing a block. + blockInvVect := wire.InvVect{ + Type: wire.InvTypeBlock, + Hash: *baseHash, + } + + // blockInvVectEncoded is the wire encoded bytes of blockInvVect. + blockInvVectEncoded := []byte{ + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + } + + tests := []struct { + in wire.InvVect // NetAddress to encode + out wire.InvVect // Expected decoded NetAddress + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteInvVect(&buf, test.pver, &test.in) + if err != nil { + t.Errorf("writeInvVect #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeInvVect #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var iv wire.InvVect + rbuf := bytes.NewReader(test.buf) + err = wire.TstReadInvVect(rbuf, test.pver, &iv) + if err != nil { + t.Errorf("readInvVect #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(iv, test.out) { + t.Errorf("readInvVect #%d\n got: %s want: %s", i, + spew.Sdump(iv), spew.Sdump(test.out)) + continue + } + } +} diff --git a/wire/message.go b/wire/message.go new file mode 100644 index 00000000..f9d03375 --- /dev/null +++ b/wire/message.go @@ -0,0 +1,369 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "fmt" + "io" + "unicode/utf8" +) + +// MessageHeaderSize is the number of bytes in a bitcoin message header. +// Bitcoin network (magic) 4 bytes + command 12 bytes + payload length 4 bytes + +// checksum 4 bytes. +const MessageHeaderSize = 24 + +// CommandSize is the fixed size of all commands in the common bitcoin message +// header. Shorter commands must be zero padded. +const CommandSize = 12 + +// MaxMessagePayload is the maximum bytes a message can be regardless of other +// individual limits imposed by messages themselves. +const MaxMessagePayload = (1024 * 1024 * 32) // 32MB + +// Commands used in bitcoin message headers which describe the type of message. +const ( + CmdVersion = "version" + CmdVerAck = "verack" + CmdGetAddr = "getaddr" + CmdAddr = "addr" + CmdGetBlocks = "getblocks" + CmdInv = "inv" + CmdGetData = "getdata" + CmdNotFound = "notfound" + CmdBlock = "block" + CmdTx = "tx" + CmdGetHeaders = "getheaders" + CmdHeaders = "headers" + CmdPing = "ping" + CmdPong = "pong" + CmdAlert = "alert" + CmdMemPool = "mempool" + CmdFilterAdd = "filteradd" + CmdFilterClear = "filterclear" + CmdFilterLoad = "filterload" + CmdMerkleBlock = "merkleblock" + CmdReject = "reject" +) + +// Message is an interface that describes a bitcoin message. A type that +// implements Message has complete control over the representation of its data +// and may therefore contain additional or fewer fields than those which +// are used directly in the protocol encoded message. +type Message interface { + BtcDecode(io.Reader, uint32) error + BtcEncode(io.Writer, uint32) error + Command() string + MaxPayloadLength(uint32) uint32 +} + +// makeEmptyMessage creates a message of the appropriate concrete type based +// on the command. +func makeEmptyMessage(command string) (Message, error) { + var msg Message + switch command { + case CmdVersion: + msg = &MsgVersion{} + + case CmdVerAck: + msg = &MsgVerAck{} + + case CmdGetAddr: + msg = &MsgGetAddr{} + + case CmdAddr: + msg = &MsgAddr{} + + case CmdGetBlocks: + msg = &MsgGetBlocks{} + + case CmdBlock: + msg = &MsgBlock{} + + case CmdInv: + msg = &MsgInv{} + + case CmdGetData: + msg = &MsgGetData{} + + case CmdNotFound: + msg = &MsgNotFound{} + + case CmdTx: + msg = &MsgTx{} + + case CmdPing: + msg = &MsgPing{} + + case CmdPong: + msg = &MsgPong{} + + case CmdGetHeaders: + msg = &MsgGetHeaders{} + + case CmdHeaders: + msg = &MsgHeaders{} + + case CmdAlert: + msg = &MsgAlert{} + + case CmdMemPool: + msg = &MsgMemPool{} + + case CmdFilterAdd: + msg = &MsgFilterAdd{} + + case CmdFilterClear: + msg = &MsgFilterClear{} + + case CmdFilterLoad: + msg = &MsgFilterLoad{} + + case CmdMerkleBlock: + msg = &MsgMerkleBlock{} + + case CmdReject: + msg = &MsgReject{} + + default: + return nil, fmt.Errorf("unhandled command [%s]", command) + } + return msg, nil +} + +// messageHeader defines the header structure for all bitcoin protocol messages. +type messageHeader struct { + magic BitcoinNet // 4 bytes + command string // 12 bytes + length uint32 // 4 bytes + checksum [4]byte // 4 bytes +} + +// readMessageHeader reads a bitcoin message header from r. +func readMessageHeader(r io.Reader) (int, *messageHeader, error) { + // Since readElements doesn't return the amount of bytes read, attempt + // to read the entire header into a buffer first in case there is a + // short read so the proper amount of read bytes are known. This works + // since the header is a fixed size. + var headerBytes [MessageHeaderSize]byte + n, err := io.ReadFull(r, headerBytes[:]) + if err != nil { + return n, nil, err + } + hr := bytes.NewReader(headerBytes[:]) + + // Create and populate a messageHeader struct from the raw header bytes. + hdr := messageHeader{} + var command [CommandSize]byte + readElements(hr, &hdr.magic, &command, &hdr.length, &hdr.checksum) + + // Strip trailing zeros from command string. + hdr.command = string(bytes.TrimRight(command[:], string(0))) + + return n, &hdr, nil +} + +// discardInput reads n bytes from reader r in chunks and discards the read +// bytes. This is used to skip payloads when various errors occur and helps +// prevent rogue nodes from causing massive memory allocation through forging +// header length. +func discardInput(r io.Reader, n uint32) { + maxSize := uint32(10 * 1024) // 10k at a time + numReads := n / maxSize + bytesRemaining := n % maxSize + if n > 0 { + buf := make([]byte, maxSize) + for i := uint32(0); i < numReads; i++ { + io.ReadFull(r, buf) + } + } + if bytesRemaining > 0 { + buf := make([]byte, bytesRemaining) + io.ReadFull(r, buf) + } +} + +// WriteMessageN writes a bitcoin Message to w including the necessary header +// information and returns the number of bytes written. This function is the +// same as WriteMessage except it also returns the number of bytes written. +func WriteMessageN(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) (int, error) { + totalBytes := 0 + + // Enforce max command size. + var command [CommandSize]byte + cmd := msg.Command() + if len(cmd) > CommandSize { + str := fmt.Sprintf("command [%s] is too long [max %v]", + cmd, CommandSize) + return totalBytes, messageError("WriteMessage", str) + } + copy(command[:], []byte(cmd)) + + // Encode the message payload. + var bw bytes.Buffer + err := msg.BtcEncode(&bw, pver) + if err != nil { + return totalBytes, err + } + payload := bw.Bytes() + lenp := len(payload) + + // Enforce maximum overall message payload. + if lenp > MaxMessagePayload { + str := fmt.Sprintf("message payload is too large - encoded "+ + "%d bytes, but maximum message payload is %d bytes", + lenp, MaxMessagePayload) + return totalBytes, messageError("WriteMessage", str) + } + + // Enforce maximum message payload based on the message type. + mpl := msg.MaxPayloadLength(pver) + if uint32(lenp) > mpl { + str := fmt.Sprintf("message payload is too large - encoded "+ + "%d bytes, but maximum message payload size for "+ + "messages of type [%s] is %d.", lenp, cmd, mpl) + return totalBytes, messageError("WriteMessage", str) + } + + // Create header for the message. + hdr := messageHeader{} + hdr.magic = btcnet + hdr.command = cmd + hdr.length = uint32(lenp) + copy(hdr.checksum[:], DoubleSha256(payload)[0:4]) + + // Encode the header for the message. This is done to a buffer + // rather than directly to the writer since writeElements doesn't + // return the number of bytes written. + hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize)) + writeElements(hw, hdr.magic, command, hdr.length, hdr.checksum) + + // Write header. + n, err := w.Write(hw.Bytes()) + if err != nil { + totalBytes += n + return totalBytes, err + } + totalBytes += n + + // Write payload. + n, err = w.Write(payload) + if err != nil { + totalBytes += n + return totalBytes, err + } + totalBytes += n + + return totalBytes, nil +} + +// WriteMessage writes a bitcoin Message to w including the necessary header +// information. This function is the same as WriteMessageN except it doesn't +// doesn't return the number of bytes written. This function is mainly provided +// for backwards compatibility with the original API, but it's also useful for +// callers that don't care about byte counts. +func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error { + _, err := WriteMessageN(w, msg, pver, btcnet) + return err +} + +// ReadMessageN reads, validates, and parses the next bitcoin Message from r for +// the provided protocol version and bitcoin network. It returns the number of +// bytes read in addition to the parsed Message and raw bytes which comprise the +// message. This function is the same as ReadMessage except it also returns the +// number of bytes read. +func ReadMessageN(r io.Reader, pver uint32, btcnet BitcoinNet) (int, Message, []byte, error) { + totalBytes := 0 + n, hdr, err := readMessageHeader(r) + if err != nil { + totalBytes += n + return totalBytes, nil, nil, err + } + totalBytes += n + + // Enforce maximum message payload. + if hdr.length > MaxMessagePayload { + str := fmt.Sprintf("message payload is too large - header "+ + "indicates %d bytes, but max message payload is %d "+ + "bytes.", hdr.length, MaxMessagePayload) + return totalBytes, nil, nil, messageError("ReadMessage", str) + + } + + // Check for messages from the wrong bitcoin network. + if hdr.magic != btcnet { + discardInput(r, hdr.length) + str := fmt.Sprintf("message from other network [%v]", hdr.magic) + return totalBytes, nil, nil, messageError("ReadMessage", str) + } + + // Check for malformed commands. + command := hdr.command + if !utf8.ValidString(command) { + discardInput(r, hdr.length) + str := fmt.Sprintf("invalid command %v", []byte(command)) + return totalBytes, nil, nil, messageError("ReadMessage", str) + } + + // Create struct of appropriate message type based on the command. + msg, err := makeEmptyMessage(command) + if err != nil { + discardInput(r, hdr.length) + return totalBytes, nil, nil, messageError("ReadMessage", + err.Error()) + } + + // Check for maximum length based on the message type as a malicious client + // could otherwise create a well-formed header and set the length to max + // numbers in order to exhaust the machine's memory. + mpl := msg.MaxPayloadLength(pver) + if hdr.length > mpl { + discardInput(r, hdr.length) + str := fmt.Sprintf("payload exceeds max length - header "+ + "indicates %v bytes, but max payload size for "+ + "messages of type [%v] is %v.", hdr.length, command, mpl) + return totalBytes, nil, nil, messageError("ReadMessage", str) + } + + // Read payload. + payload := make([]byte, hdr.length) + n, err = io.ReadFull(r, payload) + if err != nil { + totalBytes += n + return totalBytes, nil, nil, err + } + totalBytes += n + + // Test checksum. + checksum := DoubleSha256(payload)[0:4] + if !bytes.Equal(checksum[:], hdr.checksum[:]) { + str := fmt.Sprintf("payload checksum failed - header "+ + "indicates %v, but actual checksum is %v.", + hdr.checksum, checksum) + return totalBytes, nil, nil, messageError("ReadMessage", str) + } + + // Unmarshal message. NOTE: This must be a *bytes.Buffer since the + // MsgVersion BtcDecode function requires it. + pr := bytes.NewBuffer(payload) + err = msg.BtcDecode(pr, pver) + if err != nil { + return totalBytes, nil, nil, err + } + + return totalBytes, msg, payload, nil +} + +// ReadMessage reads, validates, and parses the next bitcoin Message from r for +// the provided protocol version and bitcoin network. It returns the parsed +// Message and raw bytes which comprise the message. This function only differs +// from ReadMessageN in that it doesn't return the number of bytes read. This +// function is mainly provided for backwards compatibility with the original +// API, but it's also useful for callers that don't care about byte counts. +func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) { + _, msg, buf, err := ReadMessageN(r, pver, btcnet) + return msg, buf, err +} diff --git a/wire/message_test.go b/wire/message_test.go new file mode 100644 index 00000000..8bb24c1b --- /dev/null +++ b/wire/message_test.go @@ -0,0 +1,452 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "encoding/binary" + "io" + "net" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// makeHeader is a convenience function to make a message header in the form of +// a byte slice. It is used to force errors when reading messages. +func makeHeader(btcnet wire.BitcoinNet, command string, + payloadLen uint32, checksum uint32) []byte { + + // The length of a bitcoin message header is 24 bytes. + // 4 byte magic number of the bitcoin network + 12 byte command + 4 byte + // payload length + 4 byte checksum. + buf := make([]byte, 24) + binary.LittleEndian.PutUint32(buf, uint32(btcnet)) + copy(buf[4:], []byte(command)) + binary.LittleEndian.PutUint32(buf[16:], payloadLen) + binary.LittleEndian.PutUint32(buf[20:], checksum) + return buf +} + +// TestMessage tests the Read/WriteMessage and Read/WriteMessageN API. +func TestMessage(t *testing.T) { + pver := wire.ProtocolVersion + + // Create the various types of messages to test. + + // MsgVersion. + addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} + you, err := wire.NewNetAddress(addrYou, wire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + you.Timestamp = time.Time{} // Version message has zero value timestamp. + addrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} + me, err := wire.NewNetAddress(addrMe, wire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + me.Timestamp = time.Time{} // Version message has zero value timestamp. + msgVersion := wire.NewMsgVersion(me, you, 123123, 0) + + msgVerack := wire.NewMsgVerAck() + msgGetAddr := wire.NewMsgGetAddr() + msgAddr := wire.NewMsgAddr() + msgGetBlocks := wire.NewMsgGetBlocks(&wire.ShaHash{}) + msgBlock := &blockOne + msgInv := wire.NewMsgInv() + msgGetData := wire.NewMsgGetData() + msgNotFound := wire.NewMsgNotFound() + msgTx := wire.NewMsgTx() + msgPing := wire.NewMsgPing(123123) + msgPong := wire.NewMsgPong(123123) + msgGetHeaders := wire.NewMsgGetHeaders() + msgHeaders := wire.NewMsgHeaders() + msgAlert := wire.NewMsgAlert([]byte("payload"), []byte("signature")) + msgMemPool := wire.NewMsgMemPool() + msgFilterAdd := wire.NewMsgFilterAdd([]byte{0x01}) + msgFilterClear := wire.NewMsgFilterClear() + msgFilterLoad := wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone) + bh := wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 0, 0) + msgMerkleBlock := wire.NewMsgMerkleBlock(bh) + msgReject := wire.NewMsgReject("block", wire.RejectDuplicate, "duplicate block") + + tests := []struct { + in wire.Message // Value to encode + out wire.Message // Expected decoded value + pver uint32 // Protocol version for wire encoding + btcnet wire.BitcoinNet // Network to use for wire encoding + bytes int // Expected num bytes read/written + }{ + {msgVersion, msgVersion, pver, wire.MainNet, 125}, + {msgVerack, msgVerack, pver, wire.MainNet, 24}, + {msgGetAddr, msgGetAddr, pver, wire.MainNet, 24}, + {msgAddr, msgAddr, pver, wire.MainNet, 25}, + {msgGetBlocks, msgGetBlocks, pver, wire.MainNet, 61}, + {msgBlock, msgBlock, pver, wire.MainNet, 239}, + {msgInv, msgInv, pver, wire.MainNet, 25}, + {msgGetData, msgGetData, pver, wire.MainNet, 25}, + {msgNotFound, msgNotFound, pver, wire.MainNet, 25}, + {msgTx, msgTx, pver, wire.MainNet, 34}, + {msgPing, msgPing, pver, wire.MainNet, 32}, + {msgPong, msgPong, pver, wire.MainNet, 32}, + {msgGetHeaders, msgGetHeaders, pver, wire.MainNet, 61}, + {msgHeaders, msgHeaders, pver, wire.MainNet, 25}, + {msgAlert, msgAlert, pver, wire.MainNet, 42}, + {msgMemPool, msgMemPool, pver, wire.MainNet, 24}, + {msgFilterAdd, msgFilterAdd, pver, wire.MainNet, 26}, + {msgFilterClear, msgFilterClear, pver, wire.MainNet, 24}, + {msgFilterLoad, msgFilterLoad, pver, wire.MainNet, 35}, + {msgMerkleBlock, msgMerkleBlock, pver, wire.MainNet, 110}, + {msgReject, msgReject, pver, wire.MainNet, 79}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + nw, err := wire.WriteMessageN(&buf, test.in, test.pver, test.btcnet) + if err != nil { + t.Errorf("WriteMessage #%d error %v", i, err) + continue + } + + // Ensure the number of bytes written match the expected value. + if nw != test.bytes { + t.Errorf("WriteMessage #%d unexpected num bytes "+ + "written - got %d, want %d", i, nw, test.bytes) + } + + // Decode from wire format. + rbuf := bytes.NewReader(buf.Bytes()) + nr, msg, _, err := wire.ReadMessageN(rbuf, test.pver, test.btcnet) + if err != nil { + t.Errorf("ReadMessage #%d error %v, msg %v", i, err, + spew.Sdump(msg)) + continue + } + if !reflect.DeepEqual(msg, test.out) { + t.Errorf("ReadMessage #%d\n got: %v want: %v", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + + // Ensure the number of bytes read match the expected value. + if nr != test.bytes { + t.Errorf("ReadMessage #%d unexpected num bytes read - "+ + "got %d, want %d", i, nr, test.bytes) + } + } + + // Do the same thing for Read/WriteMessage, but ignore the bytes since + // they don't return them. + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.WriteMessage(&buf, test.in, test.pver, test.btcnet) + if err != nil { + t.Errorf("WriteMessage #%d error %v", i, err) + continue + } + + // Decode from wire format. + rbuf := bytes.NewReader(buf.Bytes()) + msg, _, err := wire.ReadMessage(rbuf, test.pver, test.btcnet) + if err != nil { + t.Errorf("ReadMessage #%d error %v, msg %v", i, err, + spew.Sdump(msg)) + continue + } + if !reflect.DeepEqual(msg, test.out) { + t.Errorf("ReadMessage #%d\n got: %v want: %v", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestReadMessageWireErrors performs negative tests against wire decoding into +// concrete messages to confirm error paths work correctly. +func TestReadMessageWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + btcnet := wire.MainNet + + // Ensure message errors are as expected with no function specified. + wantErr := "something bad happened" + testErr := wire.MessageError{Description: wantErr} + if testErr.Error() != wantErr { + t.Errorf("MessageError: wrong error - got %v, want %v", + testErr.Error(), wantErr) + } + + // Ensure message errors are as expected with a function specified. + wantFunc := "foo" + testErr = wire.MessageError{Func: wantFunc, Description: wantErr} + if testErr.Error() != wantFunc+": "+wantErr { + t.Errorf("MessageError: wrong error - got %v, want %v", + testErr.Error(), wantErr) + } + + // Wire encoded bytes for main and testnet3 networks magic identifiers. + testNet3Bytes := makeHeader(wire.TestNet3, "", 0, 0) + + // Wire encoded bytes for a message that exceeds max overall message + // length. + mpl := uint32(wire.MaxMessagePayload) + exceedMaxPayloadBytes := makeHeader(btcnet, "getaddr", mpl+1, 0) + + // Wire encoded bytes for a command which is invalid utf-8. + badCommandBytes := makeHeader(btcnet, "bogus", 0, 0) + badCommandBytes[4] = 0x81 + + // Wire encoded bytes for a command which is valid, but not supported. + unsupportedCommandBytes := makeHeader(btcnet, "bogus", 0, 0) + + // Wire encoded bytes for a message which exceeds the max payload for + // a specific message type. + exceedTypePayloadBytes := makeHeader(btcnet, "getaddr", 1, 0) + + // Wire encoded bytes for a message which does not deliver the full + // payload according to the header length. + shortPayloadBytes := makeHeader(btcnet, "version", 115, 0) + + // Wire encoded bytes for a message with a bad checksum. + badChecksumBytes := makeHeader(btcnet, "version", 2, 0xbeef) + badChecksumBytes = append(badChecksumBytes, []byte{0x0, 0x0}...) + + // Wire encoded bytes for a message which has a valid header, but is + // the wrong format. An addr starts with a varint of the number of + // contained in the message. Claim there is two, but don't provide + // them. At the same time, forge the header fields so the message is + // otherwise accurate. + badMessageBytes := makeHeader(btcnet, "addr", 1, 0xeaadc31c) + badMessageBytes = append(badMessageBytes, 0x2) + + // Wire encoded bytes for a message which the header claims has 15k + // bytes of data to discard. + discardBytes := makeHeader(btcnet, "bogus", 15*1024, 0) + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + btcnet wire.BitcoinNet // Bitcoin network for wire encoding + max int // Max size of fixed buffer to induce errors + readErr error // Expected read error + bytes int // Expected num bytes read + }{ + // Latest protocol version with intentional read errors. + + // Short header. + { + []byte{}, + pver, + btcnet, + 0, + io.EOF, + 0, + }, + + // Wrong network. Want MainNet, but giving TestNet3. + { + testNet3Bytes, + pver, + btcnet, + len(testNet3Bytes), + &wire.MessageError{}, + 24, + }, + + // Exceed max overall message payload length. + { + exceedMaxPayloadBytes, + pver, + btcnet, + len(exceedMaxPayloadBytes), + &wire.MessageError{}, + 24, + }, + + // Invalid UTF-8 command. + { + badCommandBytes, + pver, + btcnet, + len(badCommandBytes), + &wire.MessageError{}, + 24, + }, + + // Valid, but unsupported command. + { + unsupportedCommandBytes, + pver, + btcnet, + len(unsupportedCommandBytes), + &wire.MessageError{}, + 24, + }, + + // Exceed max allowed payload for a message of a specific type. + { + exceedTypePayloadBytes, + pver, + btcnet, + len(exceedTypePayloadBytes), + &wire.MessageError{}, + 24, + }, + + // Message with a payload shorter than the header indicates. + { + shortPayloadBytes, + pver, + btcnet, + len(shortPayloadBytes), + io.EOF, + 24, + }, + + // Message with a bad checksum. + { + badChecksumBytes, + pver, + btcnet, + len(badChecksumBytes), + &wire.MessageError{}, + 26, + }, + + // Message with a valid header, but wrong format. + { + badMessageBytes, + pver, + btcnet, + len(badMessageBytes), + io.EOF, + 25, + }, + + // 15k bytes of data to discard. + { + discardBytes, + pver, + btcnet, + len(discardBytes), + &wire.MessageError{}, + 24, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + r := newFixedReader(test.max, test.buf) + nr, _, _, err := wire.ReadMessageN(r, test.pver, test.btcnet) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+ + "want: %T", i, err, err, test.readErr) + continue + } + + // Ensure the number of bytes written match the expected value. + if nr != test.bytes { + t.Errorf("ReadMessage #%d unexpected num bytes read - "+ + "got %d, want %d", i, nr, test.bytes) + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+ + "want: %v <%T>", i, err, err, + test.readErr, test.readErr) + continue + } + } + } +} + +// TestWriteMessageWireErrors performs negative tests against wire encoding from +// concrete messages to confirm error paths work correctly. +func TestWriteMessageWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + btcnet := wire.MainNet + wireErr := &wire.MessageError{} + + // Fake message with a command that is too long. + badCommandMsg := &fakeMessage{command: "somethingtoolong"} + + // Fake message with a problem during encoding + encodeErrMsg := &fakeMessage{forceEncodeErr: true} + + // Fake message that has payload which exceeds max overall message size. + exceedOverallPayload := make([]byte, wire.MaxMessagePayload+1) + exceedOverallPayloadErrMsg := &fakeMessage{payload: exceedOverallPayload} + + // Fake message that has payload which exceeds max allowed per message. + exceedPayload := make([]byte, 1) + exceedPayloadErrMsg := &fakeMessage{payload: exceedPayload, forceLenErr: true} + + // Fake message that is used to force errors in the header and payload + // writes. + bogusPayload := []byte{0x01, 0x02, 0x03, 0x04} + bogusMsg := &fakeMessage{command: "bogus", payload: bogusPayload} + + tests := []struct { + msg wire.Message // Message to encode + pver uint32 // Protocol version for wire encoding + btcnet wire.BitcoinNet // Bitcoin network for wire encoding + max int // Max size of fixed buffer to induce errors + err error // Expected error + bytes int // Expected num bytes written + }{ + // Command too long. + {badCommandMsg, pver, btcnet, 0, wireErr, 0}, + // Force error in payload encode. + {encodeErrMsg, pver, btcnet, 0, wireErr, 0}, + // Force error due to exceeding max overall message payload size. + {exceedOverallPayloadErrMsg, pver, btcnet, 0, wireErr, 0}, + // Force error due to exceeding max payload for message type. + {exceedPayloadErrMsg, pver, btcnet, 0, wireErr, 0}, + // Force error in header write. + {bogusMsg, pver, btcnet, 0, io.ErrShortWrite, 0}, + // Force error in payload write. + {bogusMsg, pver, btcnet, 24, io.ErrShortWrite, 24}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode wire format. + w := newFixedWriter(test.max) + nw, err := wire.WriteMessageN(w, test.msg, test.pver, test.btcnet) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("WriteMessage #%d wrong error got: %v <%T>, "+ + "want: %T", i, err, err, test.err) + continue + } + + // Ensure the number of bytes written match the expected value. + if nw != test.bytes { + t.Errorf("WriteMessage #%d unexpected num bytes "+ + "written - got %d, want %d", i, nw, test.bytes) + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.err { + t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+ + "want: %v <%T>", i, err, err, + test.err, test.err) + continue + } + } + } +} diff --git a/wire/msgaddr.go b/wire/msgaddr.go new file mode 100644 index 00000000..f0faedcd --- /dev/null +++ b/wire/msgaddr.go @@ -0,0 +1,142 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MaxAddrPerMsg is the maximum number of addresses that can be in a single +// bitcoin addr message (MsgAddr). +const MaxAddrPerMsg = 1000 + +// MsgAddr implements the Message interface and represents a bitcoin +// addr message. It is used to provide a list of known active peers on the +// network. An active peer is considered one that has transmitted a message +// within the last 3 hours. Nodes which have not transmitted in that time +// frame should be forgotten. Each message is limited to a maximum number of +// addresses, which is currently 1000. As a result, multiple messages must +// be used to relay the full list. +// +// Use the AddAddress function to build up the list of known addresses when +// sending an addr message to another peer. +type MsgAddr struct { + AddrList []*NetAddress +} + +// AddAddress adds a known active peer to the message. +func (msg *MsgAddr) AddAddress(na *NetAddress) error { + if len(msg.AddrList)+1 > MaxAddrPerMsg { + str := fmt.Sprintf("too many addresses in message [max %v]", + MaxAddrPerMsg) + return messageError("MsgAddr.AddAddress", str) + } + + msg.AddrList = append(msg.AddrList, na) + return nil +} + +// AddAddresses adds multiple known active peers to the message. +func (msg *MsgAddr) AddAddresses(netAddrs ...*NetAddress) error { + for _, na := range netAddrs { + err := msg.AddAddress(na) + if err != nil { + return err + } + } + return nil +} + +// ClearAddresses removes all addresses from the message. +func (msg *MsgAddr) ClearAddresses() { + msg.AddrList = []*NetAddress{} +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error { + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max addresses per message. + if count > MaxAddrPerMsg { + str := fmt.Sprintf("too many addresses for message "+ + "[count %v, max %v]", count, MaxAddrPerMsg) + return messageError("MsgAddr.BtcDecode", str) + } + + msg.AddrList = make([]*NetAddress, 0, count) + for i := uint64(0); i < count; i++ { + na := NetAddress{} + err := readNetAddress(r, pver, &na, true) + if err != nil { + return err + } + msg.AddAddress(&na) + } + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgAddr) BtcEncode(w io.Writer, pver uint32) error { + // Protocol versions before MultipleAddressVersion only allowed 1 address + // per message. + count := len(msg.AddrList) + if pver < MultipleAddressVersion && count > 1 { + str := fmt.Sprintf("too many addresses for message of "+ + "protocol version %v [count %v, max 1]", pver, count) + return messageError("MsgAddr.BtcEncode", str) + + } + if count > MaxAddrPerMsg { + str := fmt.Sprintf("too many addresses for message "+ + "[count %v, max %v]", count, MaxAddrPerMsg) + return messageError("MsgAddr.BtcEncode", str) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, na := range msg.AddrList { + err = writeNetAddress(w, pver, na, true) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgAddr) Command() string { + return CmdAddr +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgAddr) MaxPayloadLength(pver uint32) uint32 { + if pver < MultipleAddressVersion { + // Num addresses (varInt) + a single net addresses. + return MaxVarIntPayload + maxNetAddressPayload(pver) + } + + // Num addresses (varInt) + max allowed addresses. + return MaxVarIntPayload + (MaxAddrPerMsg * maxNetAddressPayload(pver)) +} + +// NewMsgAddr returns a new bitcoin addr message that conforms to the +// Message interface. See MsgAddr for details. +func NewMsgAddr() *MsgAddr { + return &MsgAddr{ + AddrList: make([]*NetAddress, 0, MaxAddrPerMsg), + } +} diff --git a/wire/msgaddr_test.go b/wire/msgaddr_test.go new file mode 100644 index 00000000..fae1ad41 --- /dev/null +++ b/wire/msgaddr_test.go @@ -0,0 +1,321 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "net" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestAddr tests the MsgAddr API. +func TestAddr(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "addr" + msg := wire.NewMsgAddr() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgAddr: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num addresses (varInt) + max allowed addresses. + wantPayload := uint32(30009) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure NetAddresses are added properly. + tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} + na, err := wire.NewNetAddress(tcpAddr, wire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + err = msg.AddAddress(na) + if err != nil { + t.Errorf("AddAddress: %v", err) + } + if msg.AddrList[0] != na { + t.Errorf("AddAddress: wrong address added - got %v, want %v", + spew.Sprint(msg.AddrList[0]), spew.Sprint(na)) + } + + // Ensure the address list is cleared properly. + msg.ClearAddresses() + if len(msg.AddrList) != 0 { + t.Errorf("ClearAddresses: address list is not empty - "+ + "got %v [%v], want %v", len(msg.AddrList), + spew.Sprint(msg.AddrList[0]), 0) + } + + // Ensure adding more than the max allowed addresses per message returns + // error. + for i := 0; i < wire.MaxAddrPerMsg+1; i++ { + err = msg.AddAddress(na) + } + if err == nil { + t.Errorf("AddAddress: expected error on too many addresses " + + "not received") + } + err = msg.AddAddresses(na) + if err == nil { + t.Errorf("AddAddresses: expected error on too many addresses " + + "not received") + } + + // Ensure max payload is expected value for protocol versions before + // timestamp was added to NetAddress. + // Num addresses (varInt) + max allowed addresses. + pver = wire.NetAddressTimeVersion - 1 + wantPayload = uint32(26009) + maxPayload = msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure max payload is expected value for protocol versions before + // multiple addresses were allowed. + // Num addresses (varInt) + a single net addresses. + pver = wire.MultipleAddressVersion - 1 + wantPayload = uint32(35) + maxPayload = msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + return +} + +// TestAddrWire tests the MsgAddr wire encode and decode for various numbers +// of addreses and protocol versions. +func TestAddrWire(t *testing.T) { + // A couple of NetAddresses to use for testing. + na := &wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + na2 := &wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8334, + } + + // Empty address message. + noAddr := wire.NewMsgAddr() + noAddrEncoded := []byte{ + 0x00, // Varint for number of addresses + } + + // Address message with multiple addresses. + multiAddr := wire.NewMsgAddr() + multiAddr.AddAddresses(na, na2) + multiAddrEncoded := []byte{ + 0x02, // Varint for number of addresses + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1 + 0x20, 0x8e, // Port 8334 in big-endian + + } + + tests := []struct { + in *wire.MsgAddr // Message to encode + out *wire.MsgAddr // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no addresses. + { + noAddr, + noAddr, + noAddrEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple addresses. + { + multiAddr, + multiAddr, + multiAddrEncoded, + wire.ProtocolVersion, + }, + + // Protocol version MultipleAddressVersion-1 with no addresses. + { + noAddr, + noAddr, + noAddrEncoded, + wire.MultipleAddressVersion - 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgAddr + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestAddrWireErrors performs negative tests against wire encode and decode +// of MsgAddr to confirm error paths work correctly. +func TestAddrWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverMA := wire.MultipleAddressVersion + wireErr := &wire.MessageError{} + + // A couple of NetAddresses to use for testing. + na := &wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + na2 := &wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8334, + } + + // Address message with multiple addresses. + baseAddr := wire.NewMsgAddr() + baseAddr.AddAddresses(na, na2) + baseAddrEncoded := []byte{ + 0x02, // Varint for number of addresses + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1 + 0x20, 0x8e, // Port 8334 in big-endian + + } + + // Message that forces an error by having more than the max allowed + // addresses. + maxAddr := wire.NewMsgAddr() + for i := 0; i < wire.MaxAddrPerMsg; i++ { + maxAddr.AddAddress(na) + } + maxAddr.AddrList = append(maxAddr.AddrList, na) + maxAddrEncoded := []byte{ + 0xfd, 0x03, 0xe9, // Varint for number of addresses (1001) + } + + tests := []struct { + in *wire.MsgAddr // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in addresses count + {baseAddr, baseAddrEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in address list. + {baseAddr, baseAddrEncoded, pver, 1, io.ErrShortWrite, io.EOF}, + // Force error with greater than max inventory vectors. + {maxAddr, maxAddrEncoded, pver, 3, wireErr, wireErr}, + // Force error with greater than max inventory vectors for + // protocol versions before multiple addresses were allowed. + {maxAddr, maxAddrEncoded, pverMA - 1, 3, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgAddr + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msgalert.go b/wire/msgalert.go new file mode 100644 index 00000000..ec906e19 --- /dev/null +++ b/wire/msgalert.go @@ -0,0 +1,422 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "fmt" + "io" +) + +// MsgAlert contains a payload and a signature: +// +// =============================================== +// | Field | Data Type | Size | +// =============================================== +// | payload | []uchar | ? | +// ----------------------------------------------- +// | signature | []uchar | ? | +// ----------------------------------------------- +// +// Here payload is an Alert serialized into a byte array to ensure that +// versions using incompatible alert formats can still relay +// alerts among one another. +// +// An Alert is the payload deserialized as follows: +// +// =============================================== +// | Field | Data Type | Size | +// =============================================== +// | Version | int32 | 4 | +// ----------------------------------------------- +// | RelayUntil | int64 | 8 | +// ----------------------------------------------- +// | Expiration | int64 | 8 | +// ----------------------------------------------- +// | ID | int32 | 4 | +// ----------------------------------------------- +// | Cancel | int32 | 4 | +// ----------------------------------------------- +// | SetCancel | set | ? | +// ----------------------------------------------- +// | MinVer | int32 | 4 | +// ----------------------------------------------- +// | MaxVer | int32 | 4 | +// ----------------------------------------------- +// | SetSubVer | set | ? | +// ----------------------------------------------- +// | Priority | int32 | 4 | +// ----------------------------------------------- +// | Comment | string | ? | +// ----------------------------------------------- +// | StatusBar | string | ? | +// ----------------------------------------------- +// | Reserved | string | ? | +// ----------------------------------------------- +// | Total (Fixed) | 45 | +// ----------------------------------------------- +// +// NOTE: +// * string is a VarString i.e VarInt length followed by the string itself +// * set is a VarInt followed by as many number of strings +// * set is a VarInt followed by as many number of ints +// * fixedAlertSize = 40 + 5*min(VarInt) = 40 + 5*1 = 45 +// +// Now we can define bounds on Alert size, SetCancel and SetSubVer + +// Fixed size of the alert payload +const fixedAlertSize = 45 + +// maxSignatureSize is the max size of an ECDSA signature. +// NOTE: Since this size is fixed and < 255, the size of VarInt required = 1. +const maxSignatureSize = 72 + +// maxAlertSize is the maximum size an alert. +// +// MessagePayload = VarInt(Alert) + Alert + VarInt(Signature) + Signature +// MaxMessagePayload = maxAlertSize + max(VarInt) + maxSignatureSize + 1 +const maxAlertSize = MaxMessagePayload - maxSignatureSize - MaxVarIntPayload - 1 + +// maxCountSetCancel is the maximum number of cancel IDs that could possibly +// fit into a maximum size alert. +// +// maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string) +// for caculating maximum number of cancel IDs, set all other var sizes to 0 +// maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(int32) +// x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4 +const maxCountSetCancel = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4 + +// maxCountSetSubVer is the maximum number of subversions that could possibly +// fit into a maximum size alert. +// +// maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string) +// for caculating maximum number of subversions, set all other var sizes to 0 +// maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(string) +// x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / sizeOf(string) +// subversion would typically be something like "/Satoshi:0.7.2/" (15 bytes) +// so assuming < 255 bytes, sizeOf(string) = sizeOf(uint8) + 255 = 256 +const maxCountSetSubVer = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 256 + +// Alert contains the data deserialized from the MsgAlert payload. +type Alert struct { + // Alert format version + Version int32 + + // Timestamp beyond which nodes should stop relaying this alert + RelayUntil int64 + + // Timestamp beyond which this alert is no longer in effect and + // should be ignored + Expiration int64 + + // A unique ID number for this alert + ID int32 + + // All alerts with an ID less than or equal to this number should + // cancelled, deleted and not accepted in the future + Cancel int32 + + // All alert IDs contained in this set should be cancelled as above + SetCancel []int32 + + // This alert only applies to versions greater than or equal to this + // version. Other versions should still relay it. + MinVer int32 + + // This alert only applies to versions less than or equal to this version. + // Other versions should still relay it. + MaxVer int32 + + // If this set contains any elements, then only nodes that have their + // subVer contained in this set are affected by the alert. Other versions + // should still relay it. + SetSubVer []string + + // Relative priority compared to other alerts + Priority int32 + + // A comment on the alert that is not displayed + Comment string + + // The alert message that is displayed to the user + StatusBar string + + // Reserved + Reserved string +} + +// Serialize encodes the alert to w using the alert protocol encoding format. +func (alert *Alert) Serialize(w io.Writer, pver uint32) error { + err := writeElements(w, alert.Version, alert.RelayUntil, + alert.Expiration, alert.ID, alert.Cancel) + if err != nil { + return err + } + + count := len(alert.SetCancel) + if count > maxCountSetCancel { + str := fmt.Sprintf("too many cancel alert IDs for alert "+ + "[count %v, max %v]", count, maxCountSetCancel) + return messageError("Alert.Serialize", str) + } + err = writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + for i := 0; i < int(count); i++ { + err = writeElement(w, alert.SetCancel[i]) + if err != nil { + return err + } + } + + err = writeElements(w, alert.MinVer, alert.MaxVer) + if err != nil { + return err + } + + count = len(alert.SetSubVer) + if count > maxCountSetSubVer { + str := fmt.Sprintf("too many sub versions for alert "+ + "[count %v, max %v]", count, maxCountSetSubVer) + return messageError("Alert.Serialize", str) + } + err = writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + for i := 0; i < int(count); i++ { + err = writeVarString(w, pver, alert.SetSubVer[i]) + if err != nil { + return err + } + } + + err = writeElement(w, alert.Priority) + if err != nil { + return err + } + err = writeVarString(w, pver, alert.Comment) + if err != nil { + return err + } + err = writeVarString(w, pver, alert.StatusBar) + if err != nil { + return err + } + err = writeVarString(w, pver, alert.Reserved) + if err != nil { + return err + } + return nil +} + +// Deserialize decodes from r into the receiver using the alert protocol +// encoding format. +func (alert *Alert) Deserialize(r io.Reader, pver uint32) error { + err := readElements(r, &alert.Version, &alert.RelayUntil, + &alert.Expiration, &alert.ID, &alert.Cancel) + if err != nil { + return err + } + + // SetCancel: first read a VarInt that contains + // count - the number of Cancel IDs, then + // iterate count times and read them + count, err := readVarInt(r, pver) + if err != nil { + return err + } + if count > maxCountSetCancel { + str := fmt.Sprintf("too many cancel alert IDs for alert "+ + "[count %v, max %v]", count, maxCountSetCancel) + return messageError("Alert.Deserialize", str) + } + alert.SetCancel = make([]int32, count) + for i := 0; i < int(count); i++ { + err := readElement(r, &alert.SetCancel[i]) + if err != nil { + return err + } + } + + err = readElements(r, &alert.MinVer, &alert.MaxVer) + if err != nil { + return err + } + + // SetSubVer: similar to SetCancel + // but read count number of sub-version strings + count, err = readVarInt(r, pver) + if err != nil { + return err + } + if count > maxCountSetSubVer { + str := fmt.Sprintf("too many sub versions for alert "+ + "[count %v, max %v]", count, maxCountSetSubVer) + return messageError("Alert.Deserialize", str) + } + alert.SetSubVer = make([]string, count) + for i := 0; i < int(count); i++ { + alert.SetSubVer[i], err = readVarString(r, pver) + if err != nil { + return err + } + } + + err = readElement(r, &alert.Priority) + if err != nil { + return err + } + alert.Comment, err = readVarString(r, pver) + if err != nil { + return err + } + alert.StatusBar, err = readVarString(r, pver) + if err != nil { + return err + } + alert.Reserved, err = readVarString(r, pver) + if err != nil { + return err + } + return nil +} + +// NewAlert returns an new Alert with values provided. +func NewAlert(version int32, relayUntil int64, expiration int64, + id int32, cancel int32, setCancel []int32, minVer int32, + maxVer int32, setSubVer []string, priority int32, comment string, + statusBar string) *Alert { + return &Alert{ + Version: version, + RelayUntil: relayUntil, + Expiration: expiration, + ID: id, + Cancel: cancel, + SetCancel: setCancel, + MinVer: minVer, + MaxVer: maxVer, + SetSubVer: setSubVer, + Priority: priority, + Comment: comment, + StatusBar: statusBar, + Reserved: "", + } +} + +// NewAlertFromPayload returns an Alert with values deserialized from the +// serialized payload. +func NewAlertFromPayload(serializedPayload []byte, pver uint32) (*Alert, error) { + var alert Alert + r := bytes.NewReader(serializedPayload) + err := alert.Deserialize(r, pver) + if err != nil { + return nil, err + } + return &alert, nil +} + +// MsgAlert implements the Message interface and defines a bitcoin alert +// message. +// +// This is a signed message that provides notifications that the client should +// display if the signature matches the key. bitcoind/bitcoin-qt only checks +// against a signature from the core developers. +type MsgAlert struct { + // SerializedPayload is the alert payload serialized as a string so that the + // version can change but the Alert can still be passed on by older + // clients. + SerializedPayload []byte + + // Signature is the ECDSA signature of the message. + Signature []byte + + // Deserialized Payload + Payload *Alert +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgAlert) BtcDecode(r io.Reader, pver uint32) error { + var err error + + msg.SerializedPayload, err = readVarBytes(r, pver, MaxMessagePayload, + "alert serialized payload") + if err != nil { + return err + } + + msg.Payload, err = NewAlertFromPayload(msg.SerializedPayload, pver) + if err != nil { + msg.Payload = nil + } + + msg.Signature, err = readVarBytes(r, pver, MaxMessagePayload, + "alert signature") + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgAlert) BtcEncode(w io.Writer, pver uint32) error { + var err error + var serializedpayload []byte + if msg.Payload != nil { + // try to Serialize Payload if possible + r := new(bytes.Buffer) + err = msg.Payload.Serialize(r, pver) + if err != nil { + // Serialize failed - ignore & fallback + // to SerializedPayload + serializedpayload = msg.SerializedPayload + } else { + serializedpayload = r.Bytes() + } + } else { + serializedpayload = msg.SerializedPayload + } + slen := uint64(len(serializedpayload)) + if slen == 0 { + return messageError("MsgAlert.BtcEncode", "empty serialized payload") + } + err = writeVarBytes(w, pver, serializedpayload) + if err != nil { + return err + } + err = writeVarBytes(w, pver, msg.Signature) + if err != nil { + return err + } + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgAlert) Command() string { + return CmdAlert +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgAlert) MaxPayloadLength(pver uint32) uint32 { + // Since this can vary depending on the message, make it the max + // size allowed. + return MaxMessagePayload +} + +// NewMsgAlert returns a new bitcoin alert message that conforms to the Message +// interface. See MsgAlert for details. +func NewMsgAlert(serializedPayload []byte, signature []byte) *MsgAlert { + return &MsgAlert{ + SerializedPayload: serializedPayload, + Signature: signature, + Payload: nil, + } +} diff --git a/wire/msgalert_test.go b/wire/msgalert_test.go new file mode 100644 index 00000000..deecd27b --- /dev/null +++ b/wire/msgalert_test.go @@ -0,0 +1,467 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestMsgAlert tests the MsgAlert API. +func TestMsgAlert(t *testing.T) { + pver := wire.ProtocolVersion + serializedpayload := []byte("some message") + signature := []byte("some sig") + + // Ensure we get the same payload and signature back out. + msg := wire.NewMsgAlert(serializedpayload, signature) + if !reflect.DeepEqual(msg.SerializedPayload, serializedpayload) { + t.Errorf("NewMsgAlert: wrong serializedpayload - got %v, want %v", + msg.SerializedPayload, serializedpayload) + } + if !reflect.DeepEqual(msg.Signature, signature) { + t.Errorf("NewMsgAlert: wrong signature - got %v, want %v", + msg.Signature, signature) + } + + // Ensure the command is expected value. + wantCmd := "alert" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgAlert: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value. + wantPayload := uint32(1024 * 1024 * 32) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test BtcEncode with Payload == nil + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err != nil { + t.Error(err.Error()) + } + // expected = 0x0c + serializedpayload + 0x08 + signature + expectedBuf := append([]byte{0x0c}, serializedpayload...) + expectedBuf = append(expectedBuf, []byte{0x08}...) + expectedBuf = append(expectedBuf, signature...) + if !bytes.Equal(buf.Bytes(), expectedBuf) { + t.Errorf("BtcEncode got: %s want: %s", + spew.Sdump(buf.Bytes()), spew.Sdump(expectedBuf)) + } + + // Test BtcEncode with Payload != nil + // note: Payload is an empty Alert but not nil + msg.Payload = new(wire.Alert) + buf = *new(bytes.Buffer) + err = msg.BtcEncode(&buf, pver) + if err != nil { + t.Error(err.Error()) + } + // empty Alert is 45 null bytes, see Alert comments + // for details + // expected = 0x2d + 45*0x00 + 0x08 + signature + expectedBuf = append([]byte{0x2d}, bytes.Repeat([]byte{0x00}, 45)...) + expectedBuf = append(expectedBuf, []byte{0x08}...) + expectedBuf = append(expectedBuf, signature...) + if !bytes.Equal(buf.Bytes(), expectedBuf) { + t.Errorf("BtcEncode got: %s want: %s", + spew.Sdump(buf.Bytes()), spew.Sdump(expectedBuf)) + } +} + +// TestMsgAlertWire tests the MsgAlert wire encode and decode for various protocol +// versions. +func TestMsgAlertWire(t *testing.T) { + baseMsgAlert := wire.NewMsgAlert([]byte("some payload"), []byte("somesig")) + baseMsgAlertEncoded := []byte{ + 0x0c, // Varint for payload length + 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, // "some payload" + 0x07, // Varint for signature length + 0x73, 0x6f, 0x6d, 0x65, 0x73, 0x69, 0x67, // "somesig" + } + + tests := []struct { + in *wire.MsgAlert // Message to encode + out *wire.MsgAlert // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseMsgAlert, + baseMsgAlert, + baseMsgAlertEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + baseMsgAlert, + baseMsgAlert, + baseMsgAlertEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseMsgAlert, + baseMsgAlert, + baseMsgAlertEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseMsgAlert, + baseMsgAlert, + baseMsgAlertEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseMsgAlert, + baseMsgAlert, + baseMsgAlertEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgAlert + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestMsgAlertWireErrors performs negative tests against wire encode and decode +// of MsgAlert to confirm error paths work correctly. +func TestMsgAlertWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + + baseMsgAlert := wire.NewMsgAlert([]byte("some payload"), []byte("somesig")) + baseMsgAlertEncoded := []byte{ + 0x0c, // Varint for payload length + 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, // "some payload" + 0x07, // Varint for signature length + 0x73, 0x6f, 0x6d, 0x65, 0x73, 0x69, 0x67, // "somesig" + } + + tests := []struct { + in *wire.MsgAlert // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in payload length. + {baseMsgAlert, baseMsgAlertEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in payload. + {baseMsgAlert, baseMsgAlertEncoded, pver, 1, io.ErrShortWrite, io.EOF}, + // Force error in signature length. + {baseMsgAlert, baseMsgAlertEncoded, pver, 13, io.ErrShortWrite, io.EOF}, + // Force error in signature. + {baseMsgAlert, baseMsgAlertEncoded, pver, 14, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgAlert + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } + + // Test Error on empty Payload + baseMsgAlert.SerializedPayload = []byte{} + w := new(bytes.Buffer) + err := baseMsgAlert.BtcEncode(w, pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T", + err, wire.MessageError{}) + } + + // Test Payload Serialize error + // overflow the max number of elements in SetCancel + baseMsgAlert.Payload = new(wire.Alert) + baseMsgAlert.Payload.SetCancel = make([]int32, wire.MaxCountSetCancel+1) + buf := *new(bytes.Buffer) + err = baseMsgAlert.BtcEncode(&buf, pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T", + err, wire.MessageError{}) + } + + // overflow the max number of elements in SetSubVer + baseMsgAlert.Payload = new(wire.Alert) + baseMsgAlert.Payload.SetSubVer = make([]string, wire.MaxCountSetSubVer+1) + buf = *new(bytes.Buffer) + err = baseMsgAlert.BtcEncode(&buf, pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T", + err, wire.MessageError{}) + } +} + +// TestAlert tests serialization and deserialization +// of the payload to Alert +func TestAlert(t *testing.T) { + pver := wire.ProtocolVersion + alert := wire.NewAlert( + 1, 1337093712, 1368628812, 1015, + 1013, []int32{1014}, 0, 40599, []string{"/Satoshi:0.7.2/"}, 5000, "", + "URGENT: upgrade required, see http://bitcoin.org/dos for details", + ) + w := new(bytes.Buffer) + err := alert.Serialize(w, pver) + if err != nil { + t.Error(err.Error()) + } + serializedpayload := w.Bytes() + newAlert, err := wire.NewAlertFromPayload(serializedpayload, pver) + if err != nil { + t.Error(err.Error()) + } + + if alert.Version != newAlert.Version { + t.Errorf("NewAlertFromPayload: wrong Version - got %v, want %v ", + alert.Version, newAlert.Version) + } + if alert.RelayUntil != newAlert.RelayUntil { + t.Errorf("NewAlertFromPayload: wrong RelayUntil - got %v, want %v ", + alert.RelayUntil, newAlert.RelayUntil) + } + if alert.Expiration != newAlert.Expiration { + t.Errorf("NewAlertFromPayload: wrong Expiration - got %v, want %v ", + alert.Expiration, newAlert.Expiration) + } + if alert.ID != newAlert.ID { + t.Errorf("NewAlertFromPayload: wrong ID - got %v, want %v ", + alert.ID, newAlert.ID) + } + if alert.Cancel != newAlert.Cancel { + t.Errorf("NewAlertFromPayload: wrong Cancel - got %v, want %v ", + alert.Cancel, newAlert.Cancel) + } + if len(alert.SetCancel) != len(newAlert.SetCancel) { + t.Errorf("NewAlertFromPayload: wrong number of SetCancel - got %v, want %v ", + len(alert.SetCancel), len(newAlert.SetCancel)) + } + for i := 0; i < len(alert.SetCancel); i++ { + if alert.SetCancel[i] != newAlert.SetCancel[i] { + t.Errorf("NewAlertFromPayload: wrong SetCancel[%v] - got %v, want %v ", + len(alert.SetCancel), alert.SetCancel[i], newAlert.SetCancel[i]) + } + } + if alert.MinVer != newAlert.MinVer { + t.Errorf("NewAlertFromPayload: wrong MinVer - got %v, want %v ", + alert.MinVer, newAlert.MinVer) + } + if alert.MaxVer != newAlert.MaxVer { + t.Errorf("NewAlertFromPayload: wrong MaxVer - got %v, want %v ", + alert.MaxVer, newAlert.MaxVer) + } + if len(alert.SetSubVer) != len(newAlert.SetSubVer) { + t.Errorf("NewAlertFromPayload: wrong number of SetSubVer - got %v, want %v ", + len(alert.SetSubVer), len(newAlert.SetSubVer)) + } + for i := 0; i < len(alert.SetSubVer); i++ { + if alert.SetSubVer[i] != newAlert.SetSubVer[i] { + t.Errorf("NewAlertFromPayload: wrong SetSubVer[%v] - got %v, want %v ", + len(alert.SetSubVer), alert.SetSubVer[i], newAlert.SetSubVer[i]) + } + } + if alert.Priority != newAlert.Priority { + t.Errorf("NewAlertFromPayload: wrong Priority - got %v, want %v ", + alert.Priority, newAlert.Priority) + } + if alert.Comment != newAlert.Comment { + t.Errorf("NewAlertFromPayload: wrong Comment - got %v, want %v ", + alert.Comment, newAlert.Comment) + } + if alert.StatusBar != newAlert.StatusBar { + t.Errorf("NewAlertFromPayload: wrong StatusBar - got %v, want %v ", + alert.StatusBar, newAlert.StatusBar) + } + if alert.Reserved != newAlert.Reserved { + t.Errorf("NewAlertFromPayload: wrong Reserved - got %v, want %v ", + alert.Reserved, newAlert.Reserved) + } +} + +// TestAlertErrors performs negative tests against payload serialization, +// deserialization of Alert to confirm error paths work correctly. +func TestAlertErrors(t *testing.T) { + pver := wire.ProtocolVersion + + baseAlert := wire.NewAlert( + 1, 1337093712, 1368628812, 1015, + 1013, []int32{1014}, 0, 40599, []string{"/Satoshi:0.7.2/"}, 5000, "", + "URGENT", + ) + baseAlertEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q| + 0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0x01, 0xf6, 0x03, 0x00, //|................| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0x01, 0x0f, 0x2f, 0x53, 0x61, 0x74, 0x6f, //|.........../Sato| + 0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......| + 0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.| + } + tests := []struct { + in *wire.Alert // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in Version + {baseAlert, baseAlertEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in SetCancel VarInt. + {baseAlert, baseAlertEncoded, pver, 28, io.ErrShortWrite, io.EOF}, + // Force error in SetCancel ints. + {baseAlert, baseAlertEncoded, pver, 29, io.ErrShortWrite, io.EOF}, + // Force error in MinVer + {baseAlert, baseAlertEncoded, pver, 40, io.ErrShortWrite, io.EOF}, + // Force error in SetSubVer string VarInt. + {baseAlert, baseAlertEncoded, pver, 41, io.ErrShortWrite, io.EOF}, + // Force error in SetSubVer strings. + {baseAlert, baseAlertEncoded, pver, 48, io.ErrShortWrite, io.EOF}, + // Force error in Priority + {baseAlert, baseAlertEncoded, pver, 60, io.ErrShortWrite, io.EOF}, + // Force error in Comment string. + {baseAlert, baseAlertEncoded, pver, 62, io.ErrShortWrite, io.EOF}, + // Force error in StatusBar string. + {baseAlert, baseAlertEncoded, pver, 64, io.ErrShortWrite, io.EOF}, + // Force error in Reserved string. + {baseAlert, baseAlertEncoded, pver, 70, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + w := newFixedWriter(test.max) + err := test.in.Serialize(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("Alert.Serialize #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + var alert wire.Alert + r := newFixedReader(test.max, test.buf) + err = alert.Deserialize(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("Alert.Deserialize #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } + + // overflow the max number of elements in SetCancel + // maxCountSetCancel + 1 == 8388575 == \xdf\xff\x7f\x00 + // replace bytes 29-33 + badAlertEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q| + 0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0xfe, 0xdf, 0xff, 0x7f, //|................| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0x01, 0x0f, 0x2f, 0x53, 0x61, 0x74, 0x6f, //|.........../Sato| + 0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......| + 0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.| + } + var alert wire.Alert + r := bytes.NewReader(badAlertEncoded) + err := alert.Deserialize(r, pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("Alert.Deserialize wrong error got: %T, want: %T", + err, wire.MessageError{}) + } + + // overflow the max number of elements in SetSubVer + // maxCountSetSubVer + 1 == 131071 + 1 == \x00\x00\x02\x00 + // replace bytes 42-46 + badAlertEncoded = []byte{ + 0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q| + 0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0x01, 0xf6, 0x03, 0x00, //|................| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x02, 0x00, 0x74, 0x6f, //|.........../Sato| + 0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......| + 0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.| + } + r = bytes.NewReader(badAlertEncoded) + err = alert.Deserialize(r, pver) + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("Alert.Deserialize wrong error got: %T, want: %T", + err, wire.MessageError{}) + } +} diff --git a/wire/msgblock.go b/wire/msgblock.go new file mode 100644 index 00000000..812e7bf7 --- /dev/null +++ b/wire/msgblock.go @@ -0,0 +1,250 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "fmt" + "io" +) + +// defaultTransactionAlloc is the default size used for the backing array +// for transactions. The transaction array will dynamically grow as needed, but +// this figure is intended to provide enough space for the number of +// transactions in the vast majority of blocks without needing to grow the +// backing array multiple times. +const defaultTransactionAlloc = 2048 + +// MaxBlocksPerMsg is the maximum number of blocks allowed per message. +const MaxBlocksPerMsg = 500 + +// MaxBlockPayload is the maximum bytes a block message can be in bytes. +const MaxBlockPayload = 1000000 // Not actually 1MB which would be 1024 * 1024 + +// maxTxPerBlock is the maximum number of transactions that could +// possibly fit into a block. +const maxTxPerBlock = (MaxBlockPayload / minTxPayload) + 1 + +// TxLoc holds locator data for the offset and length of where a transaction is +// located within a MsgBlock data buffer. +type TxLoc struct { + TxStart int + TxLen int +} + +// MsgBlock implements the Message interface and represents a bitcoin +// block message. It is used to deliver block and transaction information in +// response to a getdata message (MsgGetData) for a given block hash. +type MsgBlock struct { + Header BlockHeader + Transactions []*MsgTx +} + +// AddTransaction adds a transaction to the message. +func (msg *MsgBlock) AddTransaction(tx *MsgTx) error { + msg.Transactions = append(msg.Transactions, tx) + return nil + +} + +// ClearTransactions removes all transactions from the message. +func (msg *MsgBlock) ClearTransactions() { + msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc) +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +// 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) error { + err := readBlockHeader(r, pver, &msg.Header) + if err != nil { + return err + } + + txCount, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Prevent more transactions than could possibly fit into a block. + // It would be possible to cause memory exhaustion and panics without + // a sane upper bound on this count. + if txCount > maxTxPerBlock { + str := fmt.Sprintf("too many transactions to fit into a block "+ + "[count %d, max %d]", txCount, maxTxPerBlock) + return messageError("MsgBlock.BtcDecode", str) + } + + msg.Transactions = make([]*MsgTx, 0, txCount) + for i := uint64(0); i < txCount; i++ { + tx := MsgTx{} + err := tx.BtcDecode(r, pver) + if err != nil { + return err + } + msg.Transactions = append(msg.Transactions, &tx) + } + + return nil +} + +// Deserialize decodes a block from r into the receiver using a format that is +// suitable for long-term storage such as a database while respecting the +// Version field in the block. This function differs from BtcDecode in that +// BtcDecode decodes from the bitcoin wire protocol as it was sent across the +// network. The wire encoding can technically differ depending on the protocol +// version and doesn't even really need to match the format of a stored block at +// all. As of the time this comment was written, the encoded block is the same +// in both instances, but there is a distinct difference and separating the two +// allows the API to be flexible enough to deal with changes. +func (msg *MsgBlock) Deserialize(r io.Reader) error { + // 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 BtcDecode. + return msg.BtcDecode(r, 0) +} + +// DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes +// a byte buffer instead of a generic reader and returns a slice containing the start and length of +// each transaction within the raw data that is being deserialized. +func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { + fullLen := r.Len() + + // 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) + if err != nil { + return nil, err + } + + txCount, err := readVarInt(r, 0) + if err != nil { + return nil, err + } + + // Prevent more transactions than could possibly fit into a block. + // It would be possible to cause memory exhaustion and panics without + // a sane upper bound on this count. + if txCount > maxTxPerBlock { + str := fmt.Sprintf("too many transactions to fit into a block "+ + "[count %d, max %d]", txCount, maxTxPerBlock) + return nil, messageError("MsgBlock.DeserializeTxLoc", str) + } + + // Deserialize each transaction while keeping track of its location + // within the byte stream. + msg.Transactions = make([]*MsgTx, 0, txCount) + txLocs := make([]TxLoc, txCount) + for i := uint64(0); i < txCount; i++ { + txLocs[i].TxStart = fullLen - r.Len() + tx := MsgTx{} + err := tx.Deserialize(r) + if err != nil { + return nil, err + } + msg.Transactions = append(msg.Transactions, &tx) + txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart + } + + return txLocs, nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +// 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) error { + err := writeBlockHeader(w, pver, &msg.Header) + if err != nil { + return err + } + + err = writeVarInt(w, pver, uint64(len(msg.Transactions))) + if err != nil { + return err + } + + for _, tx := range msg.Transactions { + err = tx.BtcEncode(w, pver) + if err != nil { + return err + } + } + + return nil +} + +// Serialize encodes the block to w using a format that suitable for long-term +// storage such as a database while respecting the Version field in the block. +// This function differs from BtcEncode in that BtcEncode encodes the block to +// the bitcoin wire protocol in order to be sent across the network. The wire +// encoding can technically differ depending on the protocol version and doesn't +// even really need to match the format of a stored block at all. As of the +// time this comment was written, the encoded block is the same in both +// instances, but there is a distinct difference and separating the two allows +// the API to be flexible enough to deal with changes. +func (msg *MsgBlock) Serialize(w io.Writer) error { + // 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 BtcEncode. + return msg.BtcEncode(w, 0) +} + +// SerializeSize returns the number of bytes it would take to serialize the +// the block. +func (msg *MsgBlock) SerializeSize() int { + // Block header bytes + Serialized varint size for the number of + // transactions. + n := blockHeaderLen + VarIntSerializeSize(uint64(len(msg.Transactions))) + + for _, tx := range msg.Transactions { + n += tx.SerializeSize() + } + + return n +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgBlock) Command() string { + return CmdBlock +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 { + // Block header at 80 bytes + transaction count + max transactions + // which can vary up to the MaxBlockPayload (including the block header + // and transaction count). + return MaxBlockPayload +} + +// BlockSha computes the block identifier hash for this block. +func (msg *MsgBlock) BlockSha() (ShaHash, error) { + return msg.Header.BlockSha() +} + +// TxShas returns a slice of hashes of all of transactions in this block. +func (msg *MsgBlock) TxShas() ([]ShaHash, error) { + shaList := make([]ShaHash, 0, len(msg.Transactions)) + for _, tx := range msg.Transactions { + // Ignore error here since TxSha can't fail in the current + // implementation except due to run-time panics. + sha, _ := tx.TxSha() + shaList = append(shaList, sha) + } + return shaList, nil +} + +// NewMsgBlock returns a new bitcoin block message that conforms to the +// Message interface. See MsgBlock for details. +func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock { + return &MsgBlock{ + Header: *blockHeader, + Transactions: make([]*MsgTx, 0, defaultTransactionAlloc), + } +} diff --git a/wire/msgblock_test.go b/wire/msgblock_test.go new file mode 100644 index 00000000..1db70626 --- /dev/null +++ b/wire/msgblock_test.go @@ -0,0 +1,584 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestBlock tests the MsgBlock API. +func TestBlock(t *testing.T) { + pver := wire.ProtocolVersion + + // Block 1 header. + prevHash := &blockOne.Header.PrevBlock + merkleHash := &blockOne.Header.MerkleRoot + bits := blockOne.Header.Bits + nonce := blockOne.Header.Nonce + bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce) + + // Ensure the command is expected value. + wantCmd := "block" + msg := wire.NewMsgBlock(bh) + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgBlock: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num addresses (varInt) + max allowed addresses. + wantPayload := uint32(1000000) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure we get the same block header data back out. + if !reflect.DeepEqual(&msg.Header, bh) { + t.Errorf("NewMsgBlock: wrong block header - got %v, want %v", + spew.Sdump(&msg.Header), spew.Sdump(bh)) + } + + // Ensure transactions are added properly. + tx := blockOne.Transactions[0].Copy() + msg.AddTransaction(tx) + if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) { + t.Errorf("AddTransaction: wrong transactions - got %v, want %v", + spew.Sdump(msg.Transactions), + spew.Sdump(blockOne.Transactions)) + } + + // Ensure transactions are properly cleared. + msg.ClearTransactions() + if len(msg.Transactions) != 0 { + t.Errorf("ClearTransactions: wrong transactions - got %v, want %v", + len(msg.Transactions), 0) + } + + return +} + +// TestBlockTxShas tests the ability to generate a slice of all transaction +// hashes from a block accurately. +func TestBlockTxShas(t *testing.T) { + // Block 1, transaction 1 hash. + hashStr := "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" + wantHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + return + } + + wantShas := []wire.ShaHash{*wantHash} + shas, err := blockOne.TxShas() + if err != nil { + t.Errorf("TxShas: %v", err) + } + if !reflect.DeepEqual(shas, wantShas) { + t.Errorf("TxShas: wrong transaction hashes - got %v, want %v", + spew.Sdump(shas), spew.Sdump(wantShas)) + } +} + +// TestBlockSha tests the ability to generate the hash of a block accurately. +func TestBlockSha(t *testing.T) { + // Block 1 hash. + hashStr := "839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" + wantHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the hash produced is expected. + blockHash, err := blockOne.BlockSha() + if err != nil { + t.Errorf("BlockSha: %v", err) + } + if !blockHash.IsEqual(wantHash) { + t.Errorf("BlockSha: wrong hash - got %v, want %v", + spew.Sprint(blockHash), spew.Sprint(wantHash)) + } +} + +// TestBlockWire tests the MsgBlock wire encode and decode for various numbers +// of transaction inputs and outputs and protocol versions. +func TestBlockWire(t *testing.T) { + tests := []struct { + in *wire.MsgBlock // Message to encode + out *wire.MsgBlock // Expected decoded message + buf []byte // Wire encoding + txLocs []wire.TxLoc // Expected transaction locations + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgBlock + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestBlockWireErrors performs negative tests against wire encode and decode +// of MsgBlock to confirm error paths work correctly. +func TestBlockWireErrors(t *testing.T) { + // Use protocol version 60002 specifically here instead of the latest + // because the test data is using bytes encoded with that protocol + // version. + pver := uint32(60002) + + tests := []struct { + in *wire.MsgBlock // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in version. + {&blockOne, blockOneBytes, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in prev block hash. + {&blockOne, blockOneBytes, pver, 4, io.ErrShortWrite, io.EOF}, + // Force error in merkle root. + {&blockOne, blockOneBytes, pver, 36, io.ErrShortWrite, io.EOF}, + // Force error in timestamp. + {&blockOne, blockOneBytes, pver, 68, io.ErrShortWrite, io.EOF}, + // Force error in difficulty bits. + {&blockOne, blockOneBytes, pver, 72, io.ErrShortWrite, io.EOF}, + // Force error in header nonce. + {&blockOne, blockOneBytes, pver, 76, io.ErrShortWrite, io.EOF}, + // Force error in transaction count. + {&blockOne, blockOneBytes, pver, 80, io.ErrShortWrite, io.EOF}, + // Force error in transactions. + {&blockOne, blockOneBytes, pver, 81, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + var msg wire.MsgBlock + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestBlockSerialize tests MsgBlock serialize and deserialize. +func TestBlockSerialize(t *testing.T) { + tests := []struct { + in *wire.MsgBlock // Message to encode + out *wire.MsgBlock // Expected decoded message + buf []byte // Serialized data + txLocs []wire.TxLoc // Expected transaction locations + }{ + { + &blockOne, + &blockOne, + blockOneBytes, + blockOneTxLocs, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Serialize the block. + var buf bytes.Buffer + err := test.in.Serialize(&buf) + if err != nil { + t.Errorf("Serialize #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("Serialize #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Deserialize the block. + var block wire.MsgBlock + rbuf := bytes.NewReader(test.buf) + err = block.Deserialize(rbuf) + if err != nil { + t.Errorf("Deserialize #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&block, test.out) { + t.Errorf("Deserialize #%d\n got: %s want: %s", i, + spew.Sdump(&block), spew.Sdump(test.out)) + continue + } + + // Deserialize the block while gathering transaction location + // information. + var txLocBlock wire.MsgBlock + br := bytes.NewBuffer(test.buf) + txLocs, err := txLocBlock.DeserializeTxLoc(br) + if err != nil { + t.Errorf("DeserializeTxLoc #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&txLocBlock, test.out) { + t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i, + spew.Sdump(&txLocBlock), spew.Sdump(test.out)) + continue + } + if !reflect.DeepEqual(txLocs, test.txLocs) { + t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i, + spew.Sdump(txLocs), spew.Sdump(test.txLocs)) + continue + } + } +} + +// TestBlockSerializeErrors performs negative tests against wire encode and +// decode of MsgBlock to confirm error paths work correctly. +func TestBlockSerializeErrors(t *testing.T) { + tests := []struct { + in *wire.MsgBlock // Value to encode + buf []byte // Serialized data + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in version. + {&blockOne, blockOneBytes, 0, io.ErrShortWrite, io.EOF}, + // Force error in prev block hash. + {&blockOne, blockOneBytes, 4, io.ErrShortWrite, io.EOF}, + // Force error in merkle root. + {&blockOne, blockOneBytes, 36, io.ErrShortWrite, io.EOF}, + // Force error in timestamp. + {&blockOne, blockOneBytes, 68, io.ErrShortWrite, io.EOF}, + // Force error in difficulty bits. + {&blockOne, blockOneBytes, 72, io.ErrShortWrite, io.EOF}, + // Force error in header nonce. + {&blockOne, blockOneBytes, 76, io.ErrShortWrite, io.EOF}, + // Force error in transaction count. + {&blockOne, blockOneBytes, 80, io.ErrShortWrite, io.EOF}, + // Force error in transactions. + {&blockOne, blockOneBytes, 81, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Serialize the block. + w := newFixedWriter(test.max) + err := test.in.Serialize(w) + if err != test.writeErr { + t.Errorf("Serialize #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Deserialize the block. + var block wire.MsgBlock + r := newFixedReader(test.max, test.buf) + err = block.Deserialize(r) + if err != test.readErr { + t.Errorf("Deserialize #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + var txLocBlock wire.MsgBlock + br := bytes.NewBuffer(test.buf[0:test.max]) + _, err = txLocBlock.DeserializeTxLoc(br) + if err != test.readErr { + t.Errorf("DeserializeTxLoc #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestBlockOverflowErrors performs tests to ensure deserializing blocks which +// are intentionally crafted to use large values for the number of transactions +// are handled properly. This could otherwise potentially be used as an attack +// vector. +func TestBlockOverflowErrors(t *testing.T) { + // Use protocol version 70001 specifically here instead of the latest + // protocol version because the test data is using bytes encoded with + // that version. + pver := uint32(70001) + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + err error // Expected error + }{ + // Block that claims to have ~uint64(0) transactions. + { + []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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // TxnCount + }, pver, &wire.MessageError{}, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + var msg wire.MsgBlock + r := bytes.NewReader(test.buf) + err := msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, reflect.TypeOf(test.err)) + continue + } + + // Deserialize from wire format. + r = bytes.NewReader(test.buf) + err = msg.Deserialize(r) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("Deserialize #%d wrong error got: %v, want: %v", + i, err, reflect.TypeOf(test.err)) + continue + } + + // Deserialize with transaction location info from wire format. + br := bytes.NewBuffer(test.buf) + _, err = msg.DeserializeTxLoc(br) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("DeserializeTxLoc #%d wrong error got: %v, "+ + "want: %v", i, err, reflect.TypeOf(test.err)) + continue + } + } +} + +// TestBlockSerializeSize performs tests to ensure the serialize size for +// various blocks is accurate. +func TestBlockSerializeSize(t *testing.T) { + // Block with no transactions. + noTxBlock := wire.NewMsgBlock(&blockOne.Header) + + tests := []struct { + in *wire.MsgBlock // Block to encode + size int // Expected serialized size + }{ + // Block with no transactions. + {noTxBlock, 81}, + + // First block in the mainnet block chain. + {&blockOne, len(blockOneBytes)}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + serializedSize := test.in.SerializeSize() + if serializedSize != test.size { + t.Errorf("MsgBlock.SerializeSize: #%d got: %d, want: "+ + "%d", i, serializedSize, test.size) + continue + } + } +} + +var blockOne = wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 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, + }), + MerkleRoot: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, + }), + + Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST + Bits: 0x1d00ffff, // 486604799 + Nonce: 0x9962e301, // 2573394689 + }, + Transactions: []*wire.MsgTx{ + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x12a05f200, + PkScript: []byte{ + 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 + }, + }, + }, + LockTime: 0, + }, + }, +} + +// Block one serialized bytes. +var blockOneBytes = []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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0x01, // TxnCount + 0x01, 0x00, 0x00, 0x00, // Version + 0x01, // Varint for number of transaction inputs + 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 (coinbase) + 0xff, 0xff, 0xff, 0xff, // Sequence + 0x01, // Varint for number of transaction outputs + 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 uncompressed public key + 0xac, // OP_CHECKSIG + 0x00, 0x00, 0x00, 0x00, // Lock time +} + +// Transaction location information for block one transactions. +var blockOneTxLocs = []wire.TxLoc{ + {TxStart: 81, TxLen: 134}, +} diff --git a/wire/msgfilteradd.go b/wire/msgfilteradd.go new file mode 100644 index 00000000..84162efa --- /dev/null +++ b/wire/msgfilteradd.go @@ -0,0 +1,90 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +const ( + // MaxFilterAddDataSize is the maximum byte size of a data + // element to add to the Bloom filter. It is equal to the + // maximum element size of a script. + MaxFilterAddDataSize = 520 +) + +// MsgFilterAdd implements the Message interface and represents a bitcoin +// filteradd message. It is used to add a data element to an existing Bloom +// filter. +// +// This message was not added until protocol version BIP0037Version. +type MsgFilterAdd struct { + Data []byte +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgFilterAdd) BtcDecode(r io.Reader, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filteradd message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterAdd.BtcDecode", str) + } + + var err error + msg.Data, err = readVarBytes(r, pver, MaxFilterAddDataSize, + "filteradd data") + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgFilterAdd) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filteradd message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterAdd.BtcEncode", str) + } + + size := len(msg.Data) + if size > MaxFilterAddDataSize { + str := fmt.Sprintf("filteradd size too large for message "+ + "[size %v, max %v]", size, MaxFilterAddDataSize) + return messageError("MsgFilterAdd.BtcEncode", str) + } + + err := writeVarBytes(w, pver, msg.Data) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgFilterAdd) Command() string { + return CmdFilterAdd +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgFilterAdd) MaxPayloadLength(pver uint32) uint32 { + return uint32(VarIntSerializeSize(MaxFilterAddDataSize)) + + MaxFilterAddDataSize +} + +// NewMsgFilterAdd returns a new bitcoin filteradd message that conforms to the +// Message interface. See MsgFilterAdd for details. +func NewMsgFilterAdd(data []byte) *MsgFilterAdd { + return &MsgFilterAdd{ + Data: data, + } +} diff --git a/wire/msgfilteradd_test.go b/wire/msgfilteradd_test.go new file mode 100644 index 00000000..6b501c57 --- /dev/null +++ b/wire/msgfilteradd_test.go @@ -0,0 +1,189 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" +) + +// TestFilterAddLatest tests the MsgFilterAdd API against the latest protocol +// version. +func TestFilterAddLatest(t *testing.T) { + pver := wire.ProtocolVersion + + data := []byte{0x01, 0x02} + msg := wire.NewMsgFilterAdd(data) + + // Ensure the command is expected value. + wantCmd := "filteradd" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgFilterAdd: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(523) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgFilterAdd failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + var readmsg wire.MsgFilterAdd + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgFilterAdd failed [%v] err <%v>", buf, err) + } + + return +} + +// TestFilterAddCrossProtocol tests the MsgFilterAdd API when encoding with the +// latest protocol version and decoding with BIP0031Version. +func TestFilterAddCrossProtocol(t *testing.T) { + data := []byte{0x01, 0x02} + msg := wire.NewMsgFilterAdd(data) + if !bytes.Equal(msg.Data, data) { + t.Errorf("should get same data back out") + } + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgFilterAdd failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + var readmsg wire.MsgFilterAdd + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err == nil { + t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't "+ + "have %v", msg) + } + + // Since one of the protocol versions doesn't support the filteradd + // message, make sure the data didn't get encoded and decoded back out. + if bytes.Equal(msg.Data, readmsg.Data) { + t.Error("should not get same data for cross protocol") + } + +} + +// TestFilterAddMaxDataSize tests the MsgFilterAdd API maximum data size. +func TestFilterAddMaxDataSize(t *testing.T) { + data := bytes.Repeat([]byte{0xff}, 521) + msg := wire.NewMsgFilterAdd(data) + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err == nil { + t.Errorf("encode of MsgFilterAdd succeeded when it shouldn't "+ + "have %v", msg) + } + + // Decode with latest protocol version. + readbuf := bytes.NewReader(data) + err = msg.BtcDecode(readbuf, wire.ProtocolVersion) + if err == nil { + t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't "+ + "have %v", msg) + } +} + +// TestFilterAddWireErrors performs negative tests against wire encode and decode +// of MsgFilterAdd to confirm error paths work correctly. +func TestFilterAddWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverNoFilterAdd := wire.BIP0037Version - 1 + wireErr := &wire.MessageError{} + + baseData := []byte{0x01, 0x02, 0x03, 0x04} + baseFilterAdd := wire.NewMsgFilterAdd(baseData) + baseFilterAddEncoded := append([]byte{0x04}, baseData...) + + tests := []struct { + in *wire.MsgFilterAdd // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in data size. + { + baseFilterAdd, baseFilterAddEncoded, pver, 0, + io.ErrShortWrite, io.EOF, + }, + // Force error in data. + { + baseFilterAdd, baseFilterAddEncoded, pver, 1, + io.ErrShortWrite, io.EOF, + }, + // Force error due to unsupported protocol version. + { + baseFilterAdd, baseFilterAddEncoded, pverNoFilterAdd, 5, + wireErr, wireErr, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgFilterAdd + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msgfilterclear.go b/wire/msgfilterclear.go new file mode 100644 index 00000000..37da436f --- /dev/null +++ b/wire/msgfilterclear.go @@ -0,0 +1,59 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgFilterClear implements the Message interface and represents a bitcoin +// filterclear message which is used to reset a Bloom filter. +// +// This message was not added until protocol version BIP0037Version and has +// no payload. +type MsgFilterClear struct{} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgFilterClear) BtcDecode(r io.Reader, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filterclear message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterClear.BtcDecode", str) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgFilterClear) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filterclear message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterClear.BtcEncode", str) + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgFilterClear) Command() string { + return CmdFilterClear +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgFilterClear) MaxPayloadLength(pver uint32) uint32 { + return 0 +} + +// NewMsgFilterClear returns a new bitcoin filterclear message that conforms to the Message +// interface. See MsgFilterClear for details. +func NewMsgFilterClear() *MsgFilterClear { + return &MsgFilterClear{} +} diff --git a/wire/msgfilterclear_test.go b/wire/msgfilterclear_test.go new file mode 100644 index 00000000..73740f98 --- /dev/null +++ b/wire/msgfilterclear_test.go @@ -0,0 +1,197 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestFilterCLearLatest tests the MsgFilterClear API against the latest +// protocol version. +func TestFilterClearLatest(t *testing.T) { + pver := wire.ProtocolVersion + + msg := wire.NewMsgFilterClear() + + // Ensure the command is expected value. + wantCmd := "filterclear" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgFilterClear: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + return +} + +// TestFilterClearCrossProtocol tests the MsgFilterClear API when encoding with +// the latest protocol version and decoding with BIP0031Version. +func TestFilterClearCrossProtocol(t *testing.T) { + msg := wire.NewMsgFilterClear() + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgFilterClear failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + var readmsg wire.MsgFilterClear + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err == nil { + t.Errorf("decode of MsgFilterClear succeeded when it "+ + "shouldn't have %v", msg) + } +} + +// TestFilterClearWire tests the MsgFilterClear wire encode and decode for +// various protocol versions. +func TestFilterClearWire(t *testing.T) { + msgFilterClear := wire.NewMsgFilterClear() + msgFilterClearEncoded := []byte{} + + tests := []struct { + in *wire.MsgFilterClear // Message to encode + out *wire.MsgFilterClear // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgFilterClear, + msgFilterClear, + msgFilterClearEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0037Version + 1. + { + msgFilterClear, + msgFilterClear, + msgFilterClearEncoded, + wire.BIP0037Version + 1, + }, + + // Protocol version BIP0037Version. + { + msgFilterClear, + msgFilterClear, + msgFilterClearEncoded, + wire.BIP0037Version, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgFilterClear + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestFilterClearWireErrors performs negative tests against wire encode and +// decode of MsgFilterClear to confirm error paths work correctly. +func TestFilterClearWireErrors(t *testing.T) { + pverNoFilterClear := wire.BIP0037Version - 1 + wireErr := &wire.MessageError{} + + baseFilterClear := wire.NewMsgFilterClear() + baseFilterClearEncoded := []byte{} + + tests := []struct { + in *wire.MsgFilterClear // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error due to unsupported protocol version. + { + baseFilterClear, baseFilterClearEncoded, + pverNoFilterClear, 4, wireErr, wireErr, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgFilterClear + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msgfilterload.go b/wire/msgfilterload.go new file mode 100644 index 00000000..7523834d --- /dev/null +++ b/wire/msgfilterload.go @@ -0,0 +1,141 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// BloomUpdateType specifies how the filter is updated when a match is found +type BloomUpdateType uint8 + +const ( + // BloomUpdateNone indicates the filter is not adjusted when a match is + // found. + BloomUpdateNone BloomUpdateType = 0 + + // BloomUpdateAll indicates if the filter matches any data element in a + // public key script, the outpoint is serialized and inserted into the + // filter. + BloomUpdateAll BloomUpdateType = 1 + + // BloomUpdateP2PubkeyOnly indicates if the filter matches a data + // element in a public key script and the script is of the standard + // pay-to-pubkey or multisig, the outpoint is serialized and inserted + // into the filter. + BloomUpdateP2PubkeyOnly BloomUpdateType = 2 +) + +const ( + // MaxFilterLoadHashFuncs is the maximum number of hash functions to + // load into the Bloom filter. + MaxFilterLoadHashFuncs = 50 + + // MaxFilterLoadFilterSize is the maximum size in bytes a filter may be. + MaxFilterLoadFilterSize = 36000 +) + +// MsgFilterLoad implements the Message interface and represents a bitcoin +// filterload message which is used to reset a Bloom filter. +// +// This message was not added until protocol version BIP0037Version. +type MsgFilterLoad struct { + Filter []byte + HashFuncs uint32 + Tweak uint32 + Flags BloomUpdateType +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgFilterLoad) BtcDecode(r io.Reader, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filterload message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterLoad.BtcDecode", str) + } + + var err error + msg.Filter, err = readVarBytes(r, pver, MaxFilterLoadFilterSize, + "filterload filter size") + if err != nil { + return err + } + + err = readElements(r, &msg.HashFuncs, &msg.Tweak, &msg.Flags) + if err != nil { + return err + } + + if msg.HashFuncs > MaxFilterLoadHashFuncs { + str := fmt.Sprintf("too many filter hash functions for message "+ + "[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs) + return messageError("MsgFilterLoad.BtcDecode", str) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgFilterLoad) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("filterload message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFilterLoad.BtcEncode", str) + } + + size := len(msg.Filter) + if size > MaxFilterLoadFilterSize { + str := fmt.Sprintf("filterload filter size too large for message "+ + "[size %v, max %v]", size, MaxFilterLoadFilterSize) + return messageError("MsgFilterLoad.BtcEncode", str) + } + + if msg.HashFuncs > MaxFilterLoadHashFuncs { + str := fmt.Sprintf("too many filter hash functions for message "+ + "[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs) + return messageError("MsgFilterLoad.BtcEncode", str) + } + + err := writeVarBytes(w, pver, msg.Filter) + if err != nil { + return err + } + + err = writeElements(w, msg.HashFuncs, msg.Tweak, msg.Flags) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgFilterLoad) Command() string { + return CmdFilterLoad +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgFilterLoad) MaxPayloadLength(pver uint32) uint32 { + // Num filter bytes (varInt) + filter + 4 bytes hash funcs + + // 4 bytes tweak + 1 byte flags. + return uint32(VarIntSerializeSize(MaxFilterLoadFilterSize)) + + MaxFilterLoadFilterSize + 9 +} + +// NewMsgFilterLoad returns a new bitcoin filterload message that conforms to +// the Message interface. See MsgFilterLoad for details. +func NewMsgFilterLoad(filter []byte, hashFuncs uint32, tweak uint32, flags BloomUpdateType) *MsgFilterLoad { + return &MsgFilterLoad{ + Filter: filter, + HashFuncs: hashFuncs, + Tweak: tweak, + Flags: flags, + } +} diff --git a/wire/msgfilterload_test.go b/wire/msgfilterload_test.go new file mode 100644 index 00000000..a6306491 --- /dev/null +++ b/wire/msgfilterload_test.go @@ -0,0 +1,230 @@ +// Copyright (c) 2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" +) + +// TestFilterCLearLatest tests the MsgFilterLoad API against the latest protocol +// version. +func TestFilterLoadLatest(t *testing.T) { + pver := wire.ProtocolVersion + + data := []byte{0x01, 0x02} + msg := wire.NewMsgFilterLoad(data, 10, 0, 0) + + // Ensure the command is expected value. + wantCmd := "filterload" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgFilterLoad: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(36012) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayLoadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgFilterLoad failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + readmsg := wire.MsgFilterLoad{} + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgFilterLoad failed [%v] err <%v>", buf, err) + } + + return +} + +// TestFilterLoadCrossProtocol tests the MsgFilterLoad API when encoding with +// the latest protocol version and decoding with BIP0031Version. +func TestFilterLoadCrossProtocol(t *testing.T) { + data := []byte{0x01, 0x02} + msg := wire.NewMsgFilterLoad(data, 10, 0, 0) + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of NewMsgFilterLoad failed %v err <%v>", msg, + err) + } + + // Decode with old protocol version. + var readmsg wire.MsgFilterLoad + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err == nil { + t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v", + msg) + } +} + +// TestFilterLoadMaxFilterSize tests the MsgFilterLoad API maximum filter size. +func TestFilterLoadMaxFilterSize(t *testing.T) { + data := bytes.Repeat([]byte{0xff}, 36001) + msg := wire.NewMsgFilterLoad(data, 10, 0, 0) + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err == nil { + t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't "+ + "have %v", msg) + } + + // Decode with latest protocol version. + readbuf := bytes.NewReader(data) + err = msg.BtcDecode(readbuf, wire.ProtocolVersion) + if err == nil { + t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't "+ + "have %v", msg) + } +} + +// TestFilterLoadMaxHashFuncsSize tests the MsgFilterLoad API maximum hash functions. +func TestFilterLoadMaxHashFuncsSize(t *testing.T) { + data := bytes.Repeat([]byte{0xff}, 10) + msg := wire.NewMsgFilterLoad(data, 61, 0, 0) + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err == nil { + t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't have %v", + msg) + } + + newBuf := []byte{ + 0x0a, // filter size + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // filter + 0x3d, 0x00, 0x00, 0x00, // max hash funcs + 0x00, 0x00, 0x00, 0x00, // tweak + 0x00, // update Type + } + // Decode with latest protocol version. + readbuf := bytes.NewReader(newBuf) + err = msg.BtcDecode(readbuf, wire.ProtocolVersion) + if err == nil { + t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v", + msg) + } +} + +// TestFilterLoadWireErrors performs negative tests against wire encode and decode +// of MsgFilterLoad to confirm error paths work correctly. +func TestFilterLoadWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverNoFilterLoad := wire.BIP0037Version - 1 + wireErr := &wire.MessageError{} + + baseFilter := []byte{0x01, 0x02, 0x03, 0x04} + baseFilterLoad := wire.NewMsgFilterLoad(baseFilter, 10, 0, + wire.BloomUpdateNone) + baseFilterLoadEncoded := append([]byte{0x04}, baseFilter...) + baseFilterLoadEncoded = append(baseFilterLoadEncoded, + 0x00, 0x00, 0x00, 0x0a, // HashFuncs + 0x00, 0x00, 0x00, 0x00, // Tweak + 0x00) // Flags + + tests := []struct { + in *wire.MsgFilterLoad // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in filter size. + { + baseFilterLoad, baseFilterLoadEncoded, pver, 0, + io.ErrShortWrite, io.EOF, + }, + // Force error in filter. + { + baseFilterLoad, baseFilterLoadEncoded, pver, 1, + io.ErrShortWrite, io.EOF, + }, + // Force error in hash funcs. + { + baseFilterLoad, baseFilterLoadEncoded, pver, 5, + io.ErrShortWrite, io.EOF, + }, + // Force error in tweak. + { + baseFilterLoad, baseFilterLoadEncoded, pver, 9, + io.ErrShortWrite, io.EOF, + }, + // Force error in flags. + { + baseFilterLoad, baseFilterLoadEncoded, pver, 13, + io.ErrShortWrite, io.EOF, + }, + // Force error due to unsupported protocol version. + { + baseFilterLoad, baseFilterLoadEncoded, pverNoFilterLoad, + 10, wireErr, wireErr, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgFilterLoad + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msggetaddr.go b/wire/msggetaddr.go new file mode 100644 index 00000000..70589fe2 --- /dev/null +++ b/wire/msggetaddr.go @@ -0,0 +1,47 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "io" +) + +// MsgGetAddr implements the Message interface and represents a bitcoin +// getaddr message. It is used to request a list of known active peers on the +// network from a peer to help identify potential nodes. The list is returned +// via one or more addr messages (MsgAddr). +// +// This message has no payload. +type MsgGetAddr struct{} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgGetAddr) BtcDecode(r io.Reader, pver uint32) error { + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetAddr) BtcEncode(w io.Writer, pver uint32) error { + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetAddr) Command() string { + return CmdGetAddr +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetAddr) MaxPayloadLength(pver uint32) uint32 { + return 0 +} + +// NewMsgGetAddr returns a new bitcoin getaddr message that conforms to the +// Message interface. See MsgGetAddr for details. +func NewMsgGetAddr() *MsgGetAddr { + return &MsgGetAddr{} +} diff --git a/wire/msggetaddr_test.go b/wire/msggetaddr_test.go new file mode 100644 index 00000000..d49928ac --- /dev/null +++ b/wire/msggetaddr_test.go @@ -0,0 +1,123 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestGetAddr tests the MsgGetAddr API. +func TestGetAddr(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "getaddr" + msg := wire.NewMsgGetAddr() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetAddr: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num addresses (varInt) + max allowed addresses. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + return +} + +// TestGetAddrWire tests the MsgGetAddr wire encode and decode for various +// protocol versions. +func TestGetAddrWire(t *testing.T) { + msgGetAddr := wire.NewMsgGetAddr() + msgGetAddrEncoded := []byte{} + + tests := []struct { + in *wire.MsgGetAddr // Message to encode + out *wire.MsgGetAddr // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgGetAddr + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} diff --git a/wire/msggetblocks.go b/wire/msggetblocks.go new file mode 100644 index 00000000..3dbecf5d --- /dev/null +++ b/wire/msggetblocks.go @@ -0,0 +1,144 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed +// per message. +const MaxBlockLocatorsPerMsg = 500 + +// MsgGetBlocks implements the Message interface and represents a bitcoin +// getblocks message. It is used to request a list of blocks starting after the +// last known hash in the slice of block locator hashes. The list is returned +// via an inv message (MsgInv) and is limited by a specific hash to stop at or +// the maximum number of blocks per message, which is currently 500. +// +// Set the HashStop field to the hash at which to stop and use +// AddBlockLocatorHash to build up the list of block locator hashes. +// +// The algorithm for building the block locator hashes should be to add the +// hashes in reverse order until you reach the genesis block. In order to keep +// the list of locator hashes to a reasonable number of entries, first add the +// most recent 10 block hashes, then double the step each loop iteration to +// exponentially decrease the number of hashes the further away from head and +// closer to the genesis block you get. +type MsgGetBlocks struct { + ProtocolVersion uint32 + BlockLocatorHashes []*ShaHash + HashStop ShaHash +} + +// AddBlockLocatorHash adds a new block locator hash to the message. +func (msg *MsgGetBlocks) AddBlockLocatorHash(hash *ShaHash) error { + if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message [max %v]", + MaxBlockLocatorsPerMsg) + return messageError("MsgGetBlocks.AddBlockLocatorHash", str) + } + + msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) + return nil +} + +// 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) error { + err := readElement(r, &msg.ProtocolVersion) + if err != nil { + return err + } + + // Read num block locator hashes and limit to max. + count, err := readVarInt(r, pver) + if err != nil { + return err + } + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetBlocks.BtcDecode", str) + } + + msg.BlockLocatorHashes = make([]*ShaHash, 0, count) + for i := uint64(0); i < count; i++ { + sha := ShaHash{} + err := readElement(r, &sha) + if err != nil { + return err + } + msg.AddBlockLocatorHash(&sha) + } + + err = readElement(r, &msg.HashStop) + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetBlocks) BtcEncode(w io.Writer, pver uint32) error { + count := len(msg.BlockLocatorHashes) + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetBlocks.BtcEncode", str) + } + + err := writeElement(w, msg.ProtocolVersion) + if err != nil { + return err + } + + err = writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, hash := range msg.BlockLocatorHashes { + err = writeElement(w, hash) + if err != nil { + return err + } + } + + err = writeElement(w, &msg.HashStop) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetBlocks) Command() string { + return CmdGetBlocks +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetBlocks) MaxPayloadLength(pver uint32) uint32 { + // Protocol version 4 bytes + num hashes (varInt) + max block locator + // hashes + hash stop. + return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize +} + +// NewMsgGetBlocks returns a new bitcoin getblocks message that conforms to the +// Message interface using the passed parameters and defaults for the remaining +// fields. +func NewMsgGetBlocks(hashStop *ShaHash) *MsgGetBlocks { + return &MsgGetBlocks{ + ProtocolVersion: ProtocolVersion, + BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg), + HashStop: *hashStop, + } +} diff --git a/wire/msggetblocks_test.go b/wire/msggetblocks_test.go new file mode 100644 index 00000000..50234c20 --- /dev/null +++ b/wire/msggetblocks_test.go @@ -0,0 +1,390 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestGetBlocks tests the MsgGetBlocks API. +func TestGetBlocks(t *testing.T) { + pver := wire.ProtocolVersion + + // Block 99500 hash. + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + locatorHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure we get the same data back out. + msg := wire.NewMsgGetBlocks(hashStop) + if !msg.HashStop.IsEqual(hashStop) { + t.Errorf("NewMsgGetBlocks: wrong stop hash - got %v, want %v", + msg.HashStop, hashStop) + } + + // Ensure the command is expected value. + wantCmd := "getblocks" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetBlocks: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Protocol version 4 bytes + num hashes (varInt) + max block locator + // hashes + hash stop. + wantPayload := uint32(16045) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure block locator hashes are added properly. + err = msg.AddBlockLocatorHash(locatorHash) + if err != nil { + t.Errorf("AddBlockLocatorHash: %v", err) + } + if msg.BlockLocatorHashes[0] != locatorHash { + t.Errorf("AddBlockLocatorHash: wrong block locator added - "+ + "got %v, want %v", + spew.Sprint(msg.BlockLocatorHashes[0]), + spew.Sprint(locatorHash)) + } + + // Ensure adding more than the max allowed block locator hashes per + // message returns an error. + for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ { + err = msg.AddBlockLocatorHash(locatorHash) + } + if err == nil { + t.Errorf("AddBlockLocatorHash: expected error on too many " + + "block locator hashes not received") + } + + return +} + +// TestGetBlocksWire tests the MsgGetBlocks wire encode and decode for various +// numbers of block locator hashes and protocol versions. +func TestGetBlocksWire(t *testing.T) { + // Set protocol inside getblocks message. + pver := uint32(60002) + + // Block 99499 hash. + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + hashLocator, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetBlocks message with no block locators or stop hash. + noLocators := wire.NewMsgGetBlocks(&wire.ShaHash{}) + noLocators.ProtocolVersion = pver + noLocatorsEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x00, // Varint for number of block locator hashes + 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, // Hash stop + } + + // MsgGetBlocks message with multiple block locators and a stop hash. + multiLocators := wire.NewMsgGetBlocks(hashStop) + multiLocators.AddBlockLocatorHash(hashLocator2) + multiLocators.AddBlockLocatorHash(hashLocator) + multiLocators.ProtocolVersion = pver + multiLocatorsEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x02, // Varint for number of block locator hashes + 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, + 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, + 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, + 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + } + + tests := []struct { + in *wire.MsgGetBlocks // Message to encode + out *wire.MsgGetBlocks // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Versionwith multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgGetBlocks + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestGetBlocksWireErrors performs negative tests against wire encode and +// decode of MsgGetBlocks to confirm error paths work correctly. +func TestGetBlocksWireErrors(t *testing.T) { + // Set protocol inside getheaders message. Use protocol version 60002 + // specifically here instead of the latest because the test data is + // using bytes encoded with that protocol version. + pver := uint32(60002) + wireErr := &wire.MessageError{} + + // Block 99499 hash. + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + hashLocator, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetBlocks message with multiple block locators and a stop hash. + baseGetBlocks := wire.NewMsgGetBlocks(hashStop) + baseGetBlocks.ProtocolVersion = pver + baseGetBlocks.AddBlockLocatorHash(hashLocator2) + baseGetBlocks.AddBlockLocatorHash(hashLocator) + baseGetBlocksEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x02, // Varint for number of block locator hashes + 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, + 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, + 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, + 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + } + + // Message that forces an error by having more than the max allowed + // block locator hashes. + maxGetBlocks := wire.NewMsgGetBlocks(hashStop) + for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ { + maxGetBlocks.AddBlockLocatorHash(&mainNetGenesisHash) + } + maxGetBlocks.BlockLocatorHashes = append(maxGetBlocks.BlockLocatorHashes, + &mainNetGenesisHash) + maxGetBlocksEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501) + } + + tests := []struct { + in *wire.MsgGetBlocks // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in protocol version. + {baseGetBlocks, baseGetBlocksEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in block locator hash count. + {baseGetBlocks, baseGetBlocksEncoded, pver, 4, io.ErrShortWrite, io.EOF}, + // Force error in block locator hashes. + {baseGetBlocks, baseGetBlocksEncoded, pver, 5, io.ErrShortWrite, io.EOF}, + // Force error in stop hash. + {baseGetBlocks, baseGetBlocksEncoded, pver, 69, io.ErrShortWrite, io.EOF}, + // Force error with greater than max block locator hashes. + {maxGetBlocks, maxGetBlocksEncoded, pver, 7, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgGetBlocks + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msggetdata.go b/wire/msggetdata.go new file mode 100644 index 00000000..661d3234 --- /dev/null +++ b/wire/msggetdata.go @@ -0,0 +1,130 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgGetData implements the Message interface and represents a bitcoin +// getdata message. It is used to request data such as blocks and transactions +// from another peer. It should be used in response to the inv (MsgInv) message +// to request the actual data referenced by each inventory vector the receiving +// peer doesn't already have. Each message is limited to a maximum number of +// inventory vectors, which is currently 50,000. As a result, multiple messages +// must be used to request larger amounts of data. +// +// Use the AddInvVect function to build up the list of inventory vectors when +// sending a getdata message to another peer. +type MsgGetData struct { + InvList []*InvVect +} + +// AddInvVect adds an inventory vector to the message. +func (msg *MsgGetData) AddInvVect(iv *InvVect) error { + if len(msg.InvList)+1 > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [max %v]", + MaxInvPerMsg) + return messageError("MsgGetData.AddInvVect", str) + } + + msg.InvList = append(msg.InvList, iv) + return nil +} + +// 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) error { + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max inventory vectors per message. + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgGetData.BtcDecode", str) + } + + msg.InvList = make([]*InvVect, 0, count) + for i := uint64(0); i < count; i++ { + iv := InvVect{} + err := readInvVect(r, pver, &iv) + if err != nil { + return err + } + msg.AddInvVect(&iv) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetData) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max inventory vectors per message. + count := len(msg.InvList) + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgGetData.BtcEncode", str) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, iv := range msg.InvList { + err := writeInvVect(w, pver, iv) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetData) Command() string { + return CmdGetData +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetData) MaxPayloadLength(pver uint32) uint32 { + // Num inventory vectors (varInt) + max allowed inventory vectors. + return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload) +} + +// NewMsgGetData returns a new bitcoin getdata message that conforms to the +// Message interface. See MsgGetData for details. +func NewMsgGetData() *MsgGetData { + return &MsgGetData{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } +} + +// NewMsgGetDataSizeHint returns a new bitcoin getdata message that conforms to +// the Message interface. See MsgGetData for details. This function differs +// from NewMsgGetData in that it allows a default allocation size for the +// backing array which houses the inventory vector list. This allows callers +// who know in advance how large the inventory list will grow to avoid the +// overhead of growing the internal backing array several times when appending +// large amounts of inventory vectors with AddInvVect. Note that the specified +// hint is just that - a hint that is used for the default allocation size. +// Adding more (or less) inventory vectors will still work properly. The size +// hint is limited to MaxInvPerMsg. +func NewMsgGetDataSizeHint(sizeHint uint) *MsgGetData { + // Limit the specified hint to the maximum allow per message. + if sizeHint > MaxInvPerMsg { + sizeHint = MaxInvPerMsg + } + + return &MsgGetData{ + InvList: make([]*InvVect, 0, sizeHint), + } +} diff --git a/wire/msggetdata_test.go b/wire/msggetdata_test.go new file mode 100644 index 00000000..e8d38815 --- /dev/null +++ b/wire/msggetdata_test.go @@ -0,0 +1,331 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestGetData tests the MsgGetData API. +func TestGetData(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "getdata" + msg := wire.NewMsgGetData() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetData: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num inventory vectors (varInt) + max allowed inventory vectors. + wantPayload := uint32(1800009) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure inventory vectors are added properly. + hash := wire.ShaHash{} + iv := wire.NewInvVect(wire.InvTypeBlock, &hash) + err := msg.AddInvVect(iv) + if err != nil { + t.Errorf("AddInvVect: %v", err) + } + if msg.InvList[0] != iv { + t.Errorf("AddInvVect: wrong invvect added - got %v, want %v", + spew.Sprint(msg.InvList[0]), spew.Sprint(iv)) + } + + // Ensure adding more than the max allowed inventory vectors per + // message returns an error. + for i := 0; i < wire.MaxInvPerMsg; i++ { + err = msg.AddInvVect(iv) + } + if err == nil { + t.Errorf("AddInvVect: expected error on too many inventory " + + "vectors not received") + } + + // Ensure creating the message with a size hint larger than the max + // works as expected. + msg = wire.NewMsgGetDataSizeHint(wire.MaxInvPerMsg + 1) + wantCap := wire.MaxInvPerMsg + if cap(msg.InvList) != wantCap { + t.Errorf("NewMsgGetDataSizeHint: wrong cap for size hint - "+ + "got %v, want %v", cap(msg.InvList), wantCap) + } + + return +} + +// TestGetDataWire tests the MsgGetData wire encode and decode for various +// numbers of inventory vectors and protocol versions. +func TestGetDataWire(t *testing.T) { + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + iv2 := wire.NewInvVect(wire.InvTypeTx, txHash) + + // Empty MsgGetData message. + NoInv := wire.NewMsgGetData() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // MsgGetData message with multiple inventory vectors. + MultiInv := wire.NewMsgGetData() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + 0x01, 0x00, 0x00, 0x00, // InvTypeTx + 0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf, + 0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8, + 0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98, + 0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash + } + + tests := []struct { + in *wire.MsgGetData // Message to encode + out *wire.MsgGetData // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgGetData + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestGetDataWireErrors performs negative tests against wire encode and decode +// of MsgGetData to confirm error paths work correctly. +func TestGetDataWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + wireErr := &wire.MessageError{} + + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + + // Base message used to induce errors. + baseGetData := wire.NewMsgGetData() + baseGetData.AddInvVect(iv) + baseGetDataEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + } + + // Message that forces an error by having more than the max allowed inv + // vectors. + maxGetData := wire.NewMsgGetData() + for i := 0; i < wire.MaxInvPerMsg; i++ { + maxGetData.AddInvVect(iv) + } + maxGetData.InvList = append(maxGetData.InvList, iv) + maxGetDataEncoded := []byte{ + 0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001) + } + + tests := []struct { + in *wire.MsgGetData // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in inventory vector count + {baseGetData, baseGetDataEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in inventory list. + {baseGetData, baseGetDataEncoded, pver, 1, io.ErrShortWrite, io.EOF}, + // Force error with greater than max inventory vectors. + {maxGetData, maxGetDataEncoded, pver, 3, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgGetData + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msggetheaders.go b/wire/msggetheaders.go new file mode 100644 index 00000000..4b407072 --- /dev/null +++ b/wire/msggetheaders.go @@ -0,0 +1,139 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgGetHeaders implements the Message interface and represents a bitcoin +// getheaders message. It is used to request a list of block headers for +// blocks starting after the last known hash in the slice of block locator +// hashes. The list is returned via a headers message (MsgHeaders) and is +// limited by a specific hash to stop at or the maximum number of block headers +// per message, which is currently 2000. +// +// Set the HashStop field to the hash at which to stop and use +// AddBlockLocatorHash to build up the list of block locator hashes. +// +// The algorithm for building the block locator hashes should be to add the +// hashes in reverse order until you reach the genesis block. In order to keep +// the list of locator hashes to a resonable number of entries, first add the +// most recent 10 block hashes, then double the step each loop iteration to +// exponentially decrease the number of hashes the further away from head and +// closer to the genesis block you get. +type MsgGetHeaders struct { + ProtocolVersion uint32 + BlockLocatorHashes []*ShaHash + HashStop ShaHash +} + +// AddBlockLocatorHash adds a new block locator hash to the message. +func (msg *MsgGetHeaders) AddBlockLocatorHash(hash *ShaHash) error { + if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message [max %v]", + MaxBlockLocatorsPerMsg) + return messageError("MsgGetHeaders.AddBlockLocatorHash", str) + } + + msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) + return nil +} + +// 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) error { + err := readElement(r, &msg.ProtocolVersion) + if err != nil { + return err + } + + // Read num block locator hashes and limit to max. + count, err := readVarInt(r, pver) + if err != nil { + return err + } + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetHeaders.BtcDecode", str) + } + + msg.BlockLocatorHashes = make([]*ShaHash, 0, count) + for i := uint64(0); i < count; i++ { + sha := ShaHash{} + err := readElement(r, &sha) + if err != nil { + return err + } + msg.AddBlockLocatorHash(&sha) + } + + err = readElement(r, &msg.HashStop) + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max block locator hashes per message. + count := len(msg.BlockLocatorHashes) + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetHeaders.BtcEncode", str) + } + + err := writeElement(w, msg.ProtocolVersion) + if err != nil { + return err + } + + err = writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, sha := range msg.BlockLocatorHashes { + err := writeElement(w, sha) + if err != nil { + return err + } + } + + err = writeElement(w, &msg.HashStop) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetHeaders) Command() string { + return CmdGetHeaders +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetHeaders) MaxPayloadLength(pver uint32) uint32 { + // Version 4 bytes + num block locator hashes (varInt) + max allowed block + // locators + hash stop. + return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize +} + +// NewMsgGetHeaders returns a new bitcoin getheaders message that conforms to +// the Message interface. See MsgGetHeaders for details. +func NewMsgGetHeaders() *MsgGetHeaders { + return &MsgGetHeaders{ + BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg), + } +} diff --git a/wire/msggetheaders_test.go b/wire/msggetheaders_test.go new file mode 100644 index 00000000..3747aa9d --- /dev/null +++ b/wire/msggetheaders_test.go @@ -0,0 +1,381 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestGetHeaders tests the MsgGetHeader API. +func TestGetHeaders(t *testing.T) { + pver := wire.ProtocolVersion + + // Block 99500 hash. + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + locatorHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the command is expected value. + wantCmd := "getheaders" + msg := wire.NewMsgGetHeaders() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgGetHeaders: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Protocol version 4 bytes + num hashes (varInt) + max block locator + // hashes + hash stop. + wantPayload := uint32(16045) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure block locator hashes are added properly. + err = msg.AddBlockLocatorHash(locatorHash) + if err != nil { + t.Errorf("AddBlockLocatorHash: %v", err) + } + if msg.BlockLocatorHashes[0] != locatorHash { + t.Errorf("AddBlockLocatorHash: wrong block locator added - "+ + "got %v, want %v", + spew.Sprint(msg.BlockLocatorHashes[0]), + spew.Sprint(locatorHash)) + } + + // Ensure adding more than the max allowed block locator hashes per + // message returns an error. + for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ { + err = msg.AddBlockLocatorHash(locatorHash) + } + if err == nil { + t.Errorf("AddBlockLocatorHash: expected error on too many " + + "block locator hashes not received") + } + + return +} + +// TestGetHeadersWire tests the MsgGetHeaders wire encode and decode for various +// numbers of block locator hashes and protocol versions. +func TestGetHeadersWire(t *testing.T) { + // Set protocol inside getheaders message. Use protocol version 60002 + // specifically here instead of the latest because the test data is + // using bytes encoded with that protocol version. + pver := uint32(60002) + + // Block 99499 hash. + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + hashLocator, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetHeaders message with no block locators or stop hash. + noLocators := wire.NewMsgGetHeaders() + noLocators.ProtocolVersion = pver + noLocatorsEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x00, // Varint for number of block locator hashes + 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, // Hash stop + } + + // MsgGetHeaders message with multiple block locators and a stop hash. + multiLocators := wire.NewMsgGetHeaders() + multiLocators.ProtocolVersion = pver + multiLocators.HashStop = *hashStop + multiLocators.AddBlockLocatorHash(hashLocator2) + multiLocators.AddBlockLocatorHash(hashLocator) + multiLocatorsEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x02, // Varint for number of block locator hashes + 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, + 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, + 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, + 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + } + + tests := []struct { + in *wire.MsgGetHeaders // Message to encode + out *wire.MsgGetHeaders // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Versionwith multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no block locators. + { + noLocators, + noLocators, + noLocatorsEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion multiple block locators. + { + multiLocators, + multiLocators, + multiLocatorsEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgGetHeaders + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestGetHeadersWireErrors performs negative tests against wire encode and +// decode of MsgGetHeaders to confirm error paths work correctly. +func TestGetHeadersWireErrors(t *testing.T) { + // Set protocol inside getheaders message. Use protocol version 60002 + // specifically here instead of the latest because the test data is + // using bytes encoded with that protocol version. + pver := uint32(60002) + wireErr := &wire.MessageError{} + + // Block 99499 hash. + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + hashLocator, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetHeaders message with multiple block locators and a stop hash. + baseGetHeaders := wire.NewMsgGetHeaders() + baseGetHeaders.ProtocolVersion = pver + baseGetHeaders.HashStop = *hashStop + baseGetHeaders.AddBlockLocatorHash(hashLocator2) + baseGetHeaders.AddBlockLocatorHash(hashLocator) + baseGetHeadersEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x02, // Varint for number of block locator hashes + 0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63, + 0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65, + 0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b, + 0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash + 0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60, + 0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9, + 0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40, + 0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop + } + + // Message that forces an error by having more than the max allowed + // block locator hashes. + maxGetHeaders := wire.NewMsgGetHeaders() + for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ { + maxGetHeaders.AddBlockLocatorHash(&mainNetGenesisHash) + } + maxGetHeaders.BlockLocatorHashes = append(maxGetHeaders.BlockLocatorHashes, + &mainNetGenesisHash) + maxGetHeadersEncoded := []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501) + } + + tests := []struct { + in *wire.MsgGetHeaders // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in protocol version. + {baseGetHeaders, baseGetHeadersEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in block locator hash count. + {baseGetHeaders, baseGetHeadersEncoded, pver, 4, io.ErrShortWrite, io.EOF}, + // Force error in block locator hashes. + {baseGetHeaders, baseGetHeadersEncoded, pver, 5, io.ErrShortWrite, io.EOF}, + // Force error in stop hash. + {baseGetHeaders, baseGetHeadersEncoded, pver, 69, io.ErrShortWrite, io.EOF}, + // Force error with greater than max block locator hashes. + {maxGetHeaders, maxGetHeadersEncoded, pver, 7, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgGetHeaders + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msgheaders.go b/wire/msgheaders.go new file mode 100644 index 00000000..603d94ee --- /dev/null +++ b/wire/msgheaders.go @@ -0,0 +1,134 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MaxBlockHeadersPerMsg is the maximum number of block headers that can be in +// a single bitcoin headers message. +const MaxBlockHeadersPerMsg = 2000 + +// MsgHeaders implements the Message interface and represents a bitcoin headers +// message. It is used to deliver block header information in response +// to a getheaders message (MsgGetHeaders). The maximum number of block headers +// per message is currently 2000. See MsgGetHeaders for details on requesting +// the headers. +type MsgHeaders struct { + Headers []*BlockHeader +} + +// AddBlockHeader adds a new block header to the message. +func (msg *MsgHeaders) AddBlockHeader(bh *BlockHeader) error { + if len(msg.Headers)+1 > MaxBlockHeadersPerMsg { + str := fmt.Sprintf("too many block headers in message [max %v]", + MaxBlockHeadersPerMsg) + return messageError("MsgHeaders.AddBlockHeader", str) + } + + msg.Headers = append(msg.Headers, bh) + return nil +} + +// 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) error { + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max block headers per message. + if count > MaxBlockHeadersPerMsg { + str := fmt.Sprintf("too many block headers for message "+ + "[count %v, max %v]", count, MaxBlockHeadersPerMsg) + return messageError("MsgHeaders.BtcDecode", str) + } + + msg.Headers = make([]*BlockHeader, 0, count) + for i := uint64(0); i < count; i++ { + bh := BlockHeader{} + err := readBlockHeader(r, pver, &bh) + if err != nil { + return err + } + + txCount, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Ensure the transaction count is zero for headers. + if txCount > 0 { + str := fmt.Sprintf("block headers may not contain "+ + "transactions [count %v]", txCount) + return messageError("MsgHeaders.BtcDecode", str) + } + msg.AddBlockHeader(&bh) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgHeaders) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max block headers per message. + count := len(msg.Headers) + if count > MaxBlockHeadersPerMsg { + str := fmt.Sprintf("too many block headers for message "+ + "[count %v, max %v]", count, MaxBlockHeadersPerMsg) + return messageError("MsgHeaders.BtcEncode", str) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, bh := range msg.Headers { + err := writeBlockHeader(w, pver, bh) + if err != nil { + return err + } + + // The wire protocol encoding always includes a 0 for the number + // 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) + if err != nil { + return err + } + + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgHeaders) Command() string { + return CmdHeaders +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgHeaders) MaxPayloadLength(pver uint32) uint32 { + // Num headers (varInt) + max allowed headers (header length + 1 byte + // for the number of transactions which is always 0). + return MaxVarIntPayload + ((MaxBlockHeaderPayload + 1) * + MaxBlockHeadersPerMsg) +} + +// NewMsgHeaders returns a new bitcoin headers message that conforms to the +// Message interface. See MsgHeaders for details. +func NewMsgHeaders() *MsgHeaders { + return &MsgHeaders{ + Headers: make([]*BlockHeader, 0, MaxBlockHeadersPerMsg), + } +} diff --git a/wire/msgheaders_test.go b/wire/msgheaders_test.go new file mode 100644 index 00000000..afbcf6fb --- /dev/null +++ b/wire/msgheaders_test.go @@ -0,0 +1,350 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestHeaders tests the MsgHeaders API. +func TestHeaders(t *testing.T) { + pver := uint32(60002) + + // Ensure the command is expected value. + wantCmd := "headers" + msg := wire.NewMsgHeaders() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgHeaders: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num headers (varInt) + max allowed headers (header length + 1 byte + // for the number of transactions which is always 0). + wantPayload := uint32(162009) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure headers are added properly. + bh := &blockOne.Header + msg.AddBlockHeader(bh) + if !reflect.DeepEqual(msg.Headers[0], bh) { + t.Errorf("AddHeader: wrong header - got %v, want %v", + spew.Sdump(msg.Headers), + spew.Sdump(bh)) + } + + // Ensure adding more than the max allowed headers per message returns + // error. + var err error + for i := 0; i < wire.MaxBlockHeadersPerMsg+1; i++ { + err = msg.AddBlockHeader(bh) + } + if reflect.TypeOf(err) != reflect.TypeOf(&wire.MessageError{}) { + t.Errorf("AddBlockHeader: expected error on too many headers " + + "not received") + } + + return +} + +// TestHeadersWire tests the MsgHeaders wire encode and decode for various +// numbers of headers and protocol versions. +func TestHeadersWire(t *testing.T) { + hash := mainNetGenesisHash + merkleHash := blockOne.Header.MerkleRoot + bits := uint32(0x1d00ffff) + nonce := uint32(0x9962e301) + bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce) + bh.Version = blockOne.Header.Version + bh.Timestamp = blockOne.Header.Timestamp + + // Empty headers message. + noHeaders := wire.NewMsgHeaders() + noHeadersEncoded := []byte{ + 0x00, // Varint for number of headers + } + + // Headers message with one header. + oneHeader := wire.NewMsgHeaders() + oneHeader.AddBlockHeader(bh) + oneHeaderEncoded := []byte{ + 0x01, // VarInt for number of headers. + 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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0x00, // TxnCount (0 for headers message) + } + + tests := []struct { + in *wire.MsgHeaders // Message to encode + out *wire.MsgHeaders // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + wire.BIP0031Version, + }, + // Protocol version NetAddressTimeVersion with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgHeaders + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestHeadersWireErrors performs negative tests against wire encode and decode +// of MsgHeaders to confirm error paths work correctly. +func TestHeadersWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + wireErr := &wire.MessageError{} + + hash := mainNetGenesisHash + merkleHash := blockOne.Header.MerkleRoot + bits := uint32(0x1d00ffff) + nonce := uint32(0x9962e301) + bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce) + bh.Version = blockOne.Header.Version + bh.Timestamp = blockOne.Header.Timestamp + + // Headers message with one header. + oneHeader := wire.NewMsgHeaders() + oneHeader.AddBlockHeader(bh) + oneHeaderEncoded := []byte{ + 0x01, // VarInt for number of headers. + 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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0x00, // TxnCount (0 for headers message) + } + + // Message that forces an error by having more than the max allowed + // headers. + maxHeaders := wire.NewMsgHeaders() + for i := 0; i < wire.MaxBlockHeadersPerMsg; i++ { + maxHeaders.AddBlockHeader(bh) + } + maxHeaders.Headers = append(maxHeaders.Headers, bh) + maxHeadersEncoded := []byte{ + 0xfd, 0xd1, 0x07, // Varint for number of addresses (2001)7D1 + } + + // Intentionally invalid block header that has a transaction count used + // to force errors. + bhTrans := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce) + bhTrans.Version = blockOne.Header.Version + bhTrans.Timestamp = blockOne.Header.Timestamp + + transHeader := wire.NewMsgHeaders() + transHeader.AddBlockHeader(bhTrans) + transHeaderEncoded := []byte{ + 0x01, // VarInt for number of headers. + 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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0x01, // TxnCount (should be 0 for headers message, but 1 to force error) + } + + tests := []struct { + in *wire.MsgHeaders // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in header count. + {oneHeader, oneHeaderEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in block header. + {oneHeader, oneHeaderEncoded, pver, 5, io.ErrShortWrite, io.EOF}, + // Force error with greater than max headers. + {maxHeaders, maxHeadersEncoded, pver, 3, wireErr, wireErr}, + // Force error with number of transactions. + {transHeader, transHeaderEncoded, pver, 81, io.ErrShortWrite, io.EOF}, + // Force error with included transactions. + {transHeader, transHeaderEncoded, pver, len(transHeaderEncoded), nil, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgHeaders + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msginv.go b/wire/msginv.go new file mode 100644 index 00000000..4dca9dd7 --- /dev/null +++ b/wire/msginv.go @@ -0,0 +1,138 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// defaultInvListAlloc is the default size used for the backing array for an +// inventory list. The array will dynamically grow as needed, but this +// figure is intended to provide enough space for the max number of inventory +// vectors in a *typical* inventory message without needing to grow the backing +// array multiple times. Technically, the list can grow to MaxInvPerMsg, but +// rather than using that large figure, this figure more accurately reflects the +// typical case. +const defaultInvListAlloc = 1000 + +// MsgInv implements the Message interface and represents a bitcoin inv message. +// It is used to advertise a peer's known data such as blocks and transactions +// through inventory vectors. It may be sent unsolicited to inform other peers +// of the data or in response to a getblocks message (MsgGetBlocks). Each +// message is limited to a maximum number of inventory vectors, which is +// currently 50,000. +// +// Use the AddInvVect function to build up the list of inventory vectors when +// sending an inv message to another peer. +type MsgInv struct { + InvList []*InvVect +} + +// AddInvVect adds an inventory vector to the message. +func (msg *MsgInv) AddInvVect(iv *InvVect) error { + if len(msg.InvList)+1 > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [max %v]", + MaxInvPerMsg) + return messageError("MsgInv.AddInvVect", str) + } + + msg.InvList = append(msg.InvList, iv) + return nil +} + +// 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) error { + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max inventory vectors per message. + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgInv.BtcDecode", str) + } + + msg.InvList = make([]*InvVect, 0, count) + for i := uint64(0); i < count; i++ { + iv := InvVect{} + err := readInvVect(r, pver, &iv) + if err != nil { + return err + } + msg.AddInvVect(&iv) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgInv) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max inventory vectors per message. + count := len(msg.InvList) + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgInv.BtcEncode", str) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, iv := range msg.InvList { + err := writeInvVect(w, pver, iv) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgInv) Command() string { + return CmdInv +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgInv) MaxPayloadLength(pver uint32) uint32 { + // Num inventory vectors (varInt) + max allowed inventory vectors. + return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload) +} + +// NewMsgInv returns a new bitcoin inv message that conforms to the Message +// interface. See MsgInv for details. +func NewMsgInv() *MsgInv { + return &MsgInv{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } +} + +// NewMsgInvSizeHint returns a new bitcoin inv message that conforms to the +// Message interface. See MsgInv for details. This function differs from +// NewMsgInv in that it allows a default allocation size for the backing array +// which houses the inventory vector list. This allows callers who know in +// advance how large the inventory list will grow to avoid the overhead of +// growing the internal backing array several times when appending large amounts +// of inventory vectors with AddInvVect. Note that the specified hint is just +// that - a hint that is used for the default allocation size. Adding more +// (or less) inventory vectors will still work properly. The size hint is +// limited to MaxInvPerMsg. +func NewMsgInvSizeHint(sizeHint uint) *MsgInv { + // Limit the specified hint to the maximum allow per message. + if sizeHint > MaxInvPerMsg { + sizeHint = MaxInvPerMsg + } + + return &MsgInv{ + InvList: make([]*InvVect, 0, sizeHint), + } +} diff --git a/wire/msginv_test.go b/wire/msginv_test.go new file mode 100644 index 00000000..f7bec092 --- /dev/null +++ b/wire/msginv_test.go @@ -0,0 +1,332 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestInv tests the MsgInv API. +func TestInv(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "inv" + msg := wire.NewMsgInv() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgInv: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num inventory vectors (varInt) + max allowed inventory vectors. + wantPayload := uint32(1800009) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure inventory vectors are added properly. + hash := wire.ShaHash{} + iv := wire.NewInvVect(wire.InvTypeBlock, &hash) + err := msg.AddInvVect(iv) + if err != nil { + t.Errorf("AddInvVect: %v", err) + } + if msg.InvList[0] != iv { + t.Errorf("AddInvVect: wrong invvect added - got %v, want %v", + spew.Sprint(msg.InvList[0]), spew.Sprint(iv)) + } + + // Ensure adding more than the max allowed inventory vectors per + // message returns an error. + for i := 0; i < wire.MaxInvPerMsg; i++ { + err = msg.AddInvVect(iv) + } + if err == nil { + t.Errorf("AddInvVect: expected error on too many inventory " + + "vectors not received") + } + + // Ensure creating the message with a size hint larger than the max + // works as expected. + msg = wire.NewMsgInvSizeHint(wire.MaxInvPerMsg + 1) + wantCap := wire.MaxInvPerMsg + if cap(msg.InvList) != wantCap { + t.Errorf("NewMsgInvSizeHint: wrong cap for size hint - "+ + "got %v, want %v", cap(msg.InvList), wantCap) + } + + return +} + +// TestInvWire tests the MsgInv wire encode and decode for various numbers +// of inventory vectors and protocol versions. +func TestInvWire(t *testing.T) { + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + iv2 := wire.NewInvVect(wire.InvTypeTx, txHash) + + // Empty inv message. + NoInv := wire.NewMsgInv() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // Inv message with multiple inventory vectors. + MultiInv := wire.NewMsgInv() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + 0x01, 0x00, 0x00, 0x00, // InvTypeTx + 0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf, + 0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8, + 0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98, + 0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash + } + + tests := []struct { + in *wire.MsgInv // Message to encode + out *wire.MsgInv // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgInv + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestInvWireErrors performs negative tests against wire encode and decode +// of MsgInv to confirm error paths work correctly. +func TestInvWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + wireErr := &wire.MessageError{} + + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + + // Base inv message used to induce errors. + baseInv := wire.NewMsgInv() + baseInv.AddInvVect(iv) + baseInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + } + + // Inv message that forces an error by having more than the max allowed + // inv vectors. + maxInv := wire.NewMsgInv() + for i := 0; i < wire.MaxInvPerMsg; i++ { + maxInv.AddInvVect(iv) + } + maxInv.InvList = append(maxInv.InvList, iv) + maxInvEncoded := []byte{ + 0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001) + } + + tests := []struct { + in *wire.MsgInv // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in inventory vector count + {baseInv, baseInvEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in inventory list. + {baseInv, baseInvEncoded, pver, 1, io.ErrShortWrite, io.EOF}, + // Force error with greater than max inventory vectors. + {maxInv, maxInvEncoded, pver, 3, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgInv + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msgmempool.go b/wire/msgmempool.go new file mode 100644 index 00000000..74945f42 --- /dev/null +++ b/wire/msgmempool.go @@ -0,0 +1,60 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgMemPool implements the Message interface and represents a bitcoin mempool +// message. It is used to request a list of transactions still in the active +// memory pool of a relay. +// +// This message has no payload and was not added until protocol versions +// starting with BIP0035Version. +type MsgMemPool struct{} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgMemPool) BtcDecode(r io.Reader, pver uint32) error { + if pver < BIP0035Version { + str := fmt.Sprintf("mempool message invalid for protocol "+ + "version %d", pver) + return messageError("MsgMemPool.BtcDecode", str) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgMemPool) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0035Version { + str := fmt.Sprintf("mempool message invalid for protocol "+ + "version %d", pver) + return messageError("MsgMemPool.BtcEncode", str) + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgMemPool) Command() string { + return CmdMemPool +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgMemPool) MaxPayloadLength(pver uint32) uint32 { + return 0 +} + +// NewMsgMemPool returns a new bitcoin pong message that conforms to the Message +// interface. See MsgPong for details. +func NewMsgMemPool() *MsgMemPool { + return &MsgMemPool{} +} diff --git a/wire/msgmempool_test.go b/wire/msgmempool_test.go new file mode 100644 index 00000000..cf2659a2 --- /dev/null +++ b/wire/msgmempool_test.go @@ -0,0 +1,66 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "testing" + + "github.com/btcsuite/btcd/wire" +) + +func TestMemPool(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "mempool" + msg := wire.NewMsgMemPool() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgMemPool: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgMemPool failed %v err <%v>", msg, err) + } + + // Older protocol versions should fail encode since message didn't + // exist yet. + oldPver := wire.BIP0035Version - 1 + err = msg.BtcEncode(&buf, oldPver) + if err == nil { + s := "encode of MsgMemPool passed for old protocol version %v err <%v>" + t.Errorf(s, msg, err) + } + + // Test decode with latest protocol version. + readmsg := wire.NewMsgMemPool() + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgMemPool failed [%v] err <%v>", buf, err) + } + + // Older protocol versions should fail decode since message didn't + // exist yet. + err = readmsg.BtcDecode(&buf, oldPver) + if err == nil { + s := "decode of MsgMemPool passed for old protocol version %v err <%v>" + t.Errorf(s, msg, err) + } + + return +} diff --git a/wire/msgmerkleblock.go b/wire/msgmerkleblock.go new file mode 100644 index 00000000..a9086b62 --- /dev/null +++ b/wire/msgmerkleblock.go @@ -0,0 +1,163 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// maxFlagsPerMerkleBlock is the maximum number of flag bytes that could +// possibly fit into a merkle block. Since each transaction is represented by +// a single bit, this is the max number of transactions per block divided by +// 8 bits per byte. Then an extra one to cover partials. +const maxFlagsPerMerkleBlock = maxTxPerBlock / 8 + +// MsgMerkleBlock implements the Message interface and represents a bitcoin +// merkleblock message which is used to reset a Bloom filter. +// +// This message was not added until protocol version BIP0037Version. +type MsgMerkleBlock struct { + Header BlockHeader + Transactions uint32 + Hashes []*ShaHash + Flags []byte +} + +// AddTxHash adds a new transaction hash to the message. +func (msg *MsgMerkleBlock) AddTxHash(hash *ShaHash) error { + if len(msg.Hashes)+1 > maxTxPerBlock { + str := fmt.Sprintf("too many tx hashes for message [max %v]", + maxTxPerBlock) + return messageError("MsgMerkleBlock.AddTxHash", str) + } + + msg.Hashes = append(msg.Hashes, hash) + return nil +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("merkleblock message invalid for protocol "+ + "version %d", pver) + return messageError("MsgMerkleBlock.BtcDecode", str) + } + + err := readBlockHeader(r, pver, &msg.Header) + if err != nil { + return err + } + + err = readElement(r, &msg.Transactions) + if err != nil { + return err + } + + // Read num block locator hashes and limit to max. + count, err := readVarInt(r, pver) + if err != nil { + return err + } + if count > maxTxPerBlock { + str := fmt.Sprintf("too many transaction hashes for message "+ + "[count %v, max %v]", count, maxTxPerBlock) + return messageError("MsgMerkleBlock.BtcDecode", str) + } + + msg.Hashes = make([]*ShaHash, 0, count) + for i := uint64(0); i < count; i++ { + var sha ShaHash + err := readElement(r, &sha) + if err != nil { + return err + } + msg.AddTxHash(&sha) + } + + msg.Flags, err = readVarBytes(r, pver, maxFlagsPerMerkleBlock, + "merkle block flags size") + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0037Version { + str := fmt.Sprintf("merkleblock message invalid for protocol "+ + "version %d", pver) + return messageError("MsgMerkleBlock.BtcEncode", str) + } + + // Read num transaction hashes and limit to max. + numHashes := len(msg.Hashes) + if numHashes > maxTxPerBlock { + str := fmt.Sprintf("too many transaction hashes for message "+ + "[count %v, max %v]", numHashes, maxTxPerBlock) + return messageError("MsgMerkleBlock.BtcDecode", str) + } + numFlagBytes := len(msg.Flags) + if numFlagBytes > maxFlagsPerMerkleBlock { + str := fmt.Sprintf("too many flag bytes for message [count %v, "+ + "max %v]", numFlagBytes, maxFlagsPerMerkleBlock) + return messageError("MsgMerkleBlock.BtcDecode", str) + } + + err := writeBlockHeader(w, pver, &msg.Header) + if err != nil { + return err + } + + err = writeElement(w, msg.Transactions) + if err != nil { + return err + } + + err = writeVarInt(w, pver, uint64(numHashes)) + if err != nil { + return err + } + for _, hash := range msg.Hashes { + err = writeElement(w, hash) + if err != nil { + return err + } + } + + err = writeVarBytes(w, pver, msg.Flags) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgMerkleBlock) Command() string { + return CmdMerkleBlock +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 { + return MaxBlockPayload +} + +// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to +// the Message interface. See MsgMerkleBlock for details. +func NewMsgMerkleBlock(bh *BlockHeader) *MsgMerkleBlock { + return &MsgMerkleBlock{ + Header: *bh, + Transactions: 0, + Hashes: make([]*ShaHash, 0), + Flags: make([]byte, 0), + } +} diff --git a/wire/msgmerkleblock_test.go b/wire/msgmerkleblock_test.go new file mode 100644 index 00000000..e2bde90b --- /dev/null +++ b/wire/msgmerkleblock_test.go @@ -0,0 +1,426 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "crypto/rand" + "io" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestMerkleBlock tests the MsgMerkleBlock API. +func TestMerkleBlock(t *testing.T) { + pver := wire.ProtocolVersion + + // Block 1 header. + prevHash := &blockOne.Header.PrevBlock + merkleHash := &blockOne.Header.MerkleRoot + bits := blockOne.Header.Bits + nonce := blockOne.Header.Nonce + bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce) + + // Ensure the command is expected value. + wantCmd := "merkleblock" + msg := wire.NewMsgMerkleBlock(bh) + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgBlock: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num addresses (varInt) + max allowed addresses. + wantPayload := uint32(1000000) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Load maxTxPerBlock hashes + data := make([]byte, 32) + for i := 0; i < wire.MaxTxPerBlock; i++ { + rand.Read(data) + hash, err := wire.NewShaHash(data) + if err != nil { + t.Errorf("NewShaHash failed: %v\n", err) + return + } + + if err = msg.AddTxHash(hash); err != nil { + t.Errorf("AddTxHash failed: %v\n", err) + return + } + } + + // Add one more Tx to test failure. + rand.Read(data) + hash, err := wire.NewShaHash(data) + if err != nil { + t.Errorf("NewShaHash failed: %v\n", err) + return + } + + if err = msg.AddTxHash(hash); err == nil { + t.Errorf("AddTxHash succeeded when it should have failed") + return + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgMerkleBlock failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + readmsg := wire.MsgMerkleBlock{} + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgMerkleBlock failed [%v] err <%v>", buf, err) + } + + // Force extra hash to test maxTxPerBlock. + msg.Hashes = append(msg.Hashes, hash) + err = msg.BtcEncode(&buf, pver) + if err == nil { + t.Errorf("encode of MsgMerkleBlock succeeded with too many " + + "tx hashes when it should have failed") + return + } + + // Force too many flag bytes to test maxFlagsPerMerkleBlock. + // Reset the number of hashes back to a valid value. + msg.Hashes = msg.Hashes[len(msg.Hashes)-1:] + msg.Flags = make([]byte, wire.MaxFlagsPerMerkleBlock+1) + err = msg.BtcEncode(&buf, pver) + if err == nil { + t.Errorf("encode of MsgMerkleBlock succeeded with too many " + + "flag bytes when it should have failed") + return + } +} + +// TestMerkleBlockCrossProtocol tests the MsgMerkleBlock API when encoding with +// the latest protocol version and decoding with BIP0031Version. +func TestMerkleBlockCrossProtocol(t *testing.T) { + // Block 1 header. + prevHash := &blockOne.Header.PrevBlock + merkleHash := &blockOne.Header.MerkleRoot + bits := blockOne.Header.Bits + nonce := blockOne.Header.Nonce + bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce) + + msg := wire.NewMsgMerkleBlock(bh) + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of NewMsgFilterLoad failed %v err <%v>", msg, + err) + } + + // Decode with old protocol version. + var readmsg wire.MsgFilterLoad + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err == nil { + t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v", + msg) + } +} + +// TestMerkleBlockWire tests the MsgMerkleBlock wire encode and decode for +// various numbers of transaction hashes and protocol versions. +func TestMerkleBlockWire(t *testing.T) { + tests := []struct { + in *wire.MsgMerkleBlock // Message to encode + out *wire.MsgMerkleBlock // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + &merkleBlockOne, &merkleBlockOne, merkleBlockOneBytes, + wire.ProtocolVersion, + }, + + // Protocol version BIP0037Version. + { + &merkleBlockOne, &merkleBlockOne, merkleBlockOneBytes, + wire.BIP0037Version, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgMerkleBlock + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestMerkleBlockWireErrors performs negative tests against wire encode and +// decode of MsgBlock to confirm error paths work correctly. +func TestMerkleBlockWireErrors(t *testing.T) { + // Use protocol version 70001 specifically here instead of the latest + // because the test data is using bytes encoded with that protocol + // version. + pver := uint32(70001) + pverNoMerkleBlock := wire.BIP0037Version - 1 + wireErr := &wire.MessageError{} + + tests := []struct { + in *wire.MsgMerkleBlock // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in version. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 0, + io.ErrShortWrite, io.EOF, + }, + // Force error in prev block hash. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 4, + io.ErrShortWrite, io.EOF, + }, + // Force error in merkle root. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 36, + io.ErrShortWrite, io.EOF, + }, + // Force error in timestamp. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 68, + io.ErrShortWrite, io.EOF, + }, + // Force error in difficulty bits. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 72, + io.ErrShortWrite, io.EOF, + }, + // Force error in header nonce. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 76, + io.ErrShortWrite, io.EOF, + }, + // Force error in transaction count. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 80, + io.ErrShortWrite, io.EOF, + }, + // Force error in num hashes. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 84, + io.ErrShortWrite, io.EOF, + }, + // Force error in hashes. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 85, + io.ErrShortWrite, io.EOF, + }, + // Force error in num flag bytes. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 117, + io.ErrShortWrite, io.EOF, + }, + // Force error in flag bytes. + { + &merkleBlockOne, merkleBlockOneBytes, pver, 118, + io.ErrShortWrite, io.EOF, + }, + // Force error due to unsupported protocol version. + { + &merkleBlockOne, merkleBlockOneBytes, pverNoMerkleBlock, + 119, wireErr, wireErr, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgMerkleBlock + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} + +// TestMerkleBlockOverflowErrors performs tests to ensure encoding and decoding +// merkle blocks that are intentionally crafted to use large values for the +// number of hashes and flags are handled properly. This could otherwise +// potentially be used as an attack vector. +func TestMerkleBlockOverflowErrors(t *testing.T) { + // Use protocol version 70001 specifically here instead of the latest + // protocol version because the test data is using bytes encoded with + // that version. + pver := uint32(70001) + + // Create bytes for a merkle block that claims to have more than the max + // allowed tx hashes. + var buf bytes.Buffer + wire.TstWriteVarInt(&buf, pver, wire.MaxTxPerBlock+1) + numHashesOffset := 84 + exceedMaxHashes := make([]byte, numHashesOffset) + copy(exceedMaxHashes, merkleBlockOneBytes[:numHashesOffset]) + exceedMaxHashes = append(exceedMaxHashes, buf.Bytes()...) + + // Create bytes for a merkle block that claims to have more than the max + // allowed flag bytes. + buf.Reset() + wire.TstWriteVarInt(&buf, pver, wire.MaxFlagsPerMerkleBlock+1) + numFlagBytesOffset := 117 + exceedMaxFlagBytes := make([]byte, numFlagBytesOffset) + copy(exceedMaxFlagBytes, merkleBlockOneBytes[:numFlagBytesOffset]) + exceedMaxFlagBytes = append(exceedMaxFlagBytes, buf.Bytes()...) + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + err error // Expected error + }{ + // Block that claims to have more than max allowed hashes. + {exceedMaxHashes, pver, &wire.MessageError{}}, + // Block that claims to have more than max allowed flag bytes. + {exceedMaxFlagBytes, pver, &wire.MessageError{}}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + var msg wire.MsgMerkleBlock + r := bytes.NewReader(test.buf) + err := msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, reflect.TypeOf(test.err)) + continue + } + } +} + +// merkleBlockOne is a merkle block created from block one of the block chain +// where the first transaction matches. +var merkleBlockOne = wire.MsgMerkleBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 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, + }), + MerkleRoot: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, + }), + Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST + Bits: 0x1d00ffff, // 486604799 + Nonce: 0x9962e301, // 2573394689 + }, + Transactions: 1, + Hashes: []*wire.ShaHash{ + (*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy. + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, + }), + }, + Flags: []byte{0x80}, +} + +// merkleBlockOneBytes is the serialized bytes for a merkle block created from +// block one of the block chain where the first transation matches. +var merkleBlockOneBytes = []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 + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot + 0x61, 0xbc, 0x66, 0x49, // Timestamp + 0xff, 0xff, 0x00, 0x1d, // Bits + 0x01, 0xe3, 0x62, 0x99, // Nonce + 0x01, 0x00, 0x00, 0x00, // TxnCount + 0x01, // Num hashes + 0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44, + 0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67, + 0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1, + 0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // Hash + 0x01, // Num flag bytes + 0x80, // Flags +} diff --git a/wire/msgnotfound.go b/wire/msgnotfound.go new file mode 100644 index 00000000..fc1cd6fc --- /dev/null +++ b/wire/msgnotfound.go @@ -0,0 +1,107 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgNotFound defines a bitcoin notfound message which is sent in response to +// a getdata message if any of the requested data in not available on the peer. +// Each message is limited to a maximum number of inventory vectors, which is +// currently 50,000. +// +// Use the AddInvVect function to build up the list of inventory vectors when +// sending a notfound message to another peer. +type MsgNotFound struct { + InvList []*InvVect +} + +// AddInvVect adds an inventory vector to the message. +func (msg *MsgNotFound) AddInvVect(iv *InvVect) error { + if len(msg.InvList)+1 > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [max %v]", + MaxInvPerMsg) + return messageError("MsgNotFound.AddInvVect", str) + } + + msg.InvList = append(msg.InvList, iv) + return nil +} + +// 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) error { + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max inventory vectors per message. + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgNotFound.BtcDecode", str) + } + + msg.InvList = make([]*InvVect, 0, count) + for i := uint64(0); i < count; i++ { + iv := InvVect{} + err := readInvVect(r, pver, &iv) + if err != nil { + return err + } + msg.AddInvVect(&iv) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgNotFound) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max inventory vectors per message. + count := len(msg.InvList) + if count > MaxInvPerMsg { + str := fmt.Sprintf("too many invvect in message [%v]", count) + return messageError("MsgNotFound.BtcEncode", str) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, iv := range msg.InvList { + err := writeInvVect(w, pver, iv) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgNotFound) Command() string { + return CmdNotFound +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgNotFound) MaxPayloadLength(pver uint32) uint32 { + // Max var int 9 bytes + max InvVects at 36 bytes each. + // Num inventory vectors (varInt) + max allowed inventory vectors. + return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload) +} + +// NewMsgNotFound returns a new bitcoin notfound message that conforms to the +// Message interface. See MsgNotFound for details. +func NewMsgNotFound() *MsgNotFound { + return &MsgNotFound{ + InvList: make([]*InvVect, 0, defaultInvListAlloc), + } +} diff --git a/wire/msgnotfound_test.go b/wire/msgnotfound_test.go new file mode 100644 index 00000000..e6311b9c --- /dev/null +++ b/wire/msgnotfound_test.go @@ -0,0 +1,321 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestNotFound tests the MsgNotFound API. +func TestNotFound(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "notfound" + msg := wire.NewMsgNotFound() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgNotFound: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num inventory vectors (varInt) + max allowed inventory vectors. + wantPayload := uint32(1800009) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure inventory vectors are added properly. + hash := wire.ShaHash{} + iv := wire.NewInvVect(wire.InvTypeBlock, &hash) + err := msg.AddInvVect(iv) + if err != nil { + t.Errorf("AddInvVect: %v", err) + } + if msg.InvList[0] != iv { + t.Errorf("AddInvVect: wrong invvect added - got %v, want %v", + spew.Sprint(msg.InvList[0]), spew.Sprint(iv)) + } + + // Ensure adding more than the max allowed inventory vectors per + // message returns an error. + for i := 0; i < wire.MaxInvPerMsg; i++ { + err = msg.AddInvVect(iv) + } + if err == nil { + t.Errorf("AddInvVect: expected error on too many inventory " + + "vectors not received") + } + + return +} + +// TestNotFoundWire tests the MsgNotFound wire encode and decode for various +// numbers of inventory vectors and protocol versions. +func TestNotFoundWire(t *testing.T) { + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + iv2 := wire.NewInvVect(wire.InvTypeTx, txHash) + + // Empty notfound message. + NoInv := wire.NewMsgNotFound() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // NotFound message with multiple inventory vectors. + MultiInv := wire.NewMsgNotFound() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + 0x01, 0x00, 0x00, 0x00, // InvTypeTx + 0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf, + 0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8, + 0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98, + 0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash + } + + tests := []struct { + in *wire.MsgNotFound // Message to encode + out *wire.MsgNotFound // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgNotFound + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestNotFoundWireErrors performs negative tests against wire encode and decode +// of MsgNotFound to confirm error paths work correctly. +func TestNotFoundWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + wireErr := &wire.MessageError{} + + // Block 203707 hash. + hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" + blockHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := wire.NewInvVect(wire.InvTypeBlock, blockHash) + + // Base message used to induce errors. + baseNotFound := wire.NewMsgNotFound() + baseNotFound.AddInvVect(iv) + baseNotFoundEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvTypeBlock + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash + } + + // Message that forces an error by having more than the max allowed inv + // vectors. + maxNotFound := wire.NewMsgNotFound() + for i := 0; i < wire.MaxInvPerMsg; i++ { + maxNotFound.AddInvVect(iv) + } + maxNotFound.InvList = append(maxNotFound.InvList, iv) + maxNotFoundEncoded := []byte{ + 0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001) + } + + tests := []struct { + in *wire.MsgNotFound // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in inventory vector count + {baseNotFound, baseNotFoundEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in inventory list. + {baseNotFound, baseNotFoundEncoded, pver, 1, io.ErrShortWrite, io.EOF}, + // Force error with greater than max inventory vectors. + {maxNotFound, maxNotFoundEncoded, pver, 3, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgNotFound + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msgping.go b/wire/msgping.go new file mode 100644 index 00000000..33814a2d --- /dev/null +++ b/wire/msgping.go @@ -0,0 +1,87 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "io" +) + +// MsgPing implements the Message interface and represents a bitcoin ping +// message. +// +// For versions BIP0031Version and earlier, it is used primarily to confirm +// that a connection is still valid. A transmission error is typically +// interpreted as a closed connection and that the peer should be removed. +// For versions AFTER BIP0031Version it contains an identifier which can be +// returned in the pong message to determine network timing. +// +// The payload for this message just consists of a nonce used for identifying +// it later. +type MsgPing struct { + // Unique value associated with message that is used to identify + // specific ping message. + Nonce uint64 +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgPing) BtcDecode(r io.Reader, pver uint32) error { + // There was no nonce for BIP0031Version and earlier. + // 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) + if err != nil { + return err + } + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgPing) BtcEncode(w io.Writer, pver uint32) error { + // There was no nonce for BIP0031Version and earlier. + // 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) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgPing) Command() string { + return CmdPing +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgPing) MaxPayloadLength(pver uint32) uint32 { + plen := uint32(0) + // There was no nonce for BIP0031Version and earlier. + // NOTE: > is not a mistake here. The BIP0031 was defined as AFTER + // the version unlike most others. + if pver > BIP0031Version { + // Nonce 8 bytes. + plen += 8 + } + + return plen +} + +// NewMsgPing returns a new bitcoin ping message that conforms to the Message +// interface. See MsgPing for details. +func NewMsgPing(nonce uint64) *MsgPing { + return &MsgPing{ + Nonce: nonce, + } +} diff --git a/wire/msgping_test.go b/wire/msgping_test.go new file mode 100644 index 00000000..f7087dd2 --- /dev/null +++ b/wire/msgping_test.go @@ -0,0 +1,243 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestPing tests the MsgPing API against the latest protocol version. +func TestPing(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure we get the same nonce back out. + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := wire.NewMsgPing(nonce) + if msg.Nonce != nonce { + t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", + msg.Nonce, nonce) + } + + // Ensure the command is expected value. + wantCmd := "ping" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgPing: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(8) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + return +} + +// TestPingBIP0031 tests the MsgPing API against the protocol version +// BIP0031Version. +func TestPingBIP0031(t *testing.T) { + // Use the protocol version just prior to BIP0031Version changes. + pver := wire.BIP0031Version + + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := wire.NewMsgPing(nonce) + if msg.Nonce != nonce { + t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", + msg.Nonce, nonce) + } + + // Ensure max payload is expected value for old protocol version. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with old protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgPing failed %v err <%v>", msg, err) + } + + // Test decode with old protocol version. + readmsg := wire.NewMsgPing(0) + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err) + } + + // Since this protocol version doesn't support the nonce, make sure + // it didn't get encoded and decoded back out. + if msg.Nonce == readmsg.Nonce { + t.Errorf("Should not get same nonce for protocol version %d", pver) + } + + return +} + +// TestPingCrossProtocol tests the MsgPing API when encoding with the latest +// protocol version and decoding with BIP0031Version. +func TestPingCrossProtocol(t *testing.T) { + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := wire.NewMsgPing(nonce) + if msg.Nonce != nonce { + t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", + msg.Nonce, nonce) + } + + // Encode with latest protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgPing failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + readmsg := wire.NewMsgPing(0) + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err != nil { + t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err) + } + + // Since one of the protocol versions doesn't support the nonce, make + // sure it didn't get encoded and decoded back out. + if msg.Nonce == readmsg.Nonce { + t.Error("Should not get same nonce for cross protocol") + } +} + +// TestPingWire tests the MsgPing wire encode and decode for various protocol +// versions. +func TestPingWire(t *testing.T) { + tests := []struct { + in wire.MsgPing // Message to encode + out wire.MsgPing // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + wire.MsgPing{Nonce: 123123}, // 0x1e0f3 + wire.MsgPing{Nonce: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + wire.ProtocolVersion, + }, + + // Protocol version BIP0031Version+1 + { + wire.MsgPing{Nonce: 456456}, // 0x6f708 + wire.MsgPing{Nonce: 456456}, // 0x6f708 + []byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + wire.BIP0031Version + 1, + }, + + // Protocol version BIP0031Version + { + wire.MsgPing{Nonce: 789789}, // 0xc0d1d + wire.MsgPing{Nonce: 0}, // No nonce for pver + []byte{}, // No nonce for pver + wire.BIP0031Version, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgPing + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestPingWireErrors performs negative tests against wire encode and decode +// of MsgPing to confirm error paths work correctly. +func TestPingWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + + tests := []struct { + in *wire.MsgPing // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + { + &wire.MsgPing{Nonce: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00}, + pver, + 2, + io.ErrShortWrite, + io.ErrUnexpectedEOF, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + var msg wire.MsgPing + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} diff --git a/wire/msgpong.go b/wire/msgpong.go new file mode 100644 index 00000000..1795feb9 --- /dev/null +++ b/wire/msgpong.go @@ -0,0 +1,88 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgPong implements the Message interface and represents a bitcoin pong +// message which is used primarily to confirm that a connection is still valid +// in response to a bitcoin ping message (MsgPing). +// +// This message was not added until protocol versions AFTER BIP0031Version. +type MsgPong struct { + // Unique value associated with message that is used to identify + // specific ping message. + Nonce uint64 +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgPong) BtcDecode(r io.Reader, pver uint32) error { + // NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER + // the version unlike most others. + if pver <= BIP0031Version { + str := fmt.Sprintf("pong message invalid for protocol "+ + "version %d", pver) + return messageError("MsgPong.BtcDecode", str) + } + + err := readElement(r, &msg.Nonce) + if err != nil { + return err + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgPong) BtcEncode(w io.Writer, pver uint32) error { + // NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER + // the version unlike most others. + if pver <= BIP0031Version { + str := fmt.Sprintf("pong message invalid for protocol "+ + "version %d", pver) + return messageError("MsgPong.BtcEncode", str) + } + + err := writeElement(w, msg.Nonce) + if err != nil { + return err + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgPong) Command() string { + return CmdPong +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgPong) MaxPayloadLength(pver uint32) uint32 { + plen := uint32(0) + // The pong message did not exist for BIP0031Version and earlier. + // NOTE: > is not a mistake here. The BIP0031 was defined as AFTER + // the version unlike most others. + if pver > BIP0031Version { + // Nonce 8 bytes. + plen += 8 + } + + return plen +} + +// NewMsgPong returns a new bitcoin pong message that conforms to the Message +// interface. See MsgPong for details. +func NewMsgPong(nonce uint64) *MsgPong { + return &MsgPong{ + Nonce: nonce, + } +} diff --git a/wire/msgpong_test.go b/wire/msgpong_test.go new file mode 100644 index 00000000..8b9a8e6f --- /dev/null +++ b/wire/msgpong_test.go @@ -0,0 +1,276 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestPongLatest tests the MsgPong API against the latest protocol version. +func TestPongLatest(t *testing.T) { + pver := wire.ProtocolVersion + + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: error generating nonce: %v", err) + } + msg := wire.NewMsgPong(nonce) + if msg.Nonce != nonce { + t.Errorf("NewMsgPong: wrong nonce - got %v, want %v", + msg.Nonce, nonce) + } + + // Ensure the command is expected value. + wantCmd := "pong" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgPong: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(8) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgPong failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + readmsg := wire.NewMsgPong(0) + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgPong failed [%v] err <%v>", buf, err) + } + + // Ensure nonce is the same. + if msg.Nonce != readmsg.Nonce { + t.Errorf("Should get same nonce for protocol version %d", pver) + } + + return +} + +// TestPongBIP0031 tests the MsgPong API against the protocol version +// BIP0031Version. +func TestPongBIP0031(t *testing.T) { + // Use the protocol version just prior to BIP0031Version changes. + pver := wire.BIP0031Version + + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("Error generating nonce: %v", err) + } + msg := wire.NewMsgPong(nonce) + if msg.Nonce != nonce { + t.Errorf("Should get same nonce back out.") + } + + // Ensure max payload is expected value for old protocol version. + size := msg.MaxPayloadLength(pver) + if size != 0 { + t.Errorf("Max length should be 0 for pong protocol version %d.", + pver) + } + + // Test encode with old protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, pver) + if err == nil { + t.Errorf("encode of MsgPong succeeded when it shouldn't have %v", + msg) + } + + // Test decode with old protocol version. + readmsg := wire.NewMsgPong(0) + err = readmsg.BtcDecode(&buf, pver) + if err == nil { + t.Errorf("decode of MsgPong succeeded when it shouldn't have %v", + spew.Sdump(buf)) + } + + // Since this protocol version doesn't support pong, make sure the + // nonce didn't get encoded and decoded back out. + if msg.Nonce == readmsg.Nonce { + t.Errorf("Should not get same nonce for protocol version %d", pver) + } + + return +} + +// TestPongCrossProtocol tests the MsgPong API when encoding with the latest +// protocol version and decoding with BIP0031Version. +func TestPongCrossProtocol(t *testing.T) { + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("Error generating nonce: %v", err) + } + msg := wire.NewMsgPong(nonce) + if msg.Nonce != nonce { + t.Errorf("Should get same nonce back out.") + } + + // Encode with latest protocol version. + var buf bytes.Buffer + err = msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgPong failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + readmsg := wire.NewMsgPong(0) + err = readmsg.BtcDecode(&buf, wire.BIP0031Version) + if err == nil { + t.Errorf("encode of MsgPong succeeded when it shouldn't have %v", + msg) + } + + // Since one of the protocol versions doesn't support the pong message, + // make sure the nonce didn't get encoded and decoded back out. + if msg.Nonce == readmsg.Nonce { + t.Error("Should not get same nonce for cross protocol") + } +} + +// TestPongWire tests the MsgPong wire encode and decode for various protocol +// versions. +func TestPongWire(t *testing.T) { + tests := []struct { + in wire.MsgPong // Message to encode + out wire.MsgPong // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + wire.MsgPong{Nonce: 123123}, // 0x1e0f3 + wire.MsgPong{Nonce: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + wire.ProtocolVersion, + }, + + // Protocol version BIP0031Version+1 + { + wire.MsgPong{Nonce: 456456}, // 0x6f708 + wire.MsgPong{Nonce: 456456}, // 0x6f708 + []byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + wire.BIP0031Version + 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgPong + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestPongWireErrors performs negative tests against wire encode and decode +// of MsgPong to confirm error paths work correctly. +func TestPongWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverNoPong := wire.BIP0031Version + wireErr := &wire.MessageError{} + + basePong := wire.NewMsgPong(123123) // 0x1e0f3 + basePongEncoded := []byte{ + 0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + tests := []struct { + in *wire.MsgPong // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in nonce. + {basePong, basePongEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error due to unsupported protocol version. + {basePong, basePongEncoded, pverNoPong, 4, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgPong + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/msgreject.go b/wire/msgreject.go new file mode 100644 index 00000000..a9c68b96 --- /dev/null +++ b/wire/msgreject.go @@ -0,0 +1,184 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// RejectCode represents a numeric value by which a remote peer indicates +// why a message was rejected. +type RejectCode uint8 + +// These constants define the various supported reject codes. +const ( + RejectMalformed RejectCode = 0x01 + RejectInvalid RejectCode = 0x10 + RejectObsolete RejectCode = 0x11 + RejectDuplicate RejectCode = 0x12 + RejectNonstandard RejectCode = 0x40 + RejectDust RejectCode = 0x41 + RejectInsufficientFee RejectCode = 0x42 + RejectCheckpoint RejectCode = 0x43 +) + +// Map of reject codes back strings for pretty printing. +var rejectCodeStrings = map[RejectCode]string{ + RejectMalformed: "REJECT_MALFORMED", + RejectInvalid: "REJECT_INVALID", + RejectObsolete: "REJECT_OBSOLETE", + RejectDuplicate: "REJECT_DUPLICATE", + RejectNonstandard: "REJECT_NONSTANDARD", + RejectDust: "REJECT_DUST", + RejectInsufficientFee: "REJECT_INSUFFICIENTFEE", + RejectCheckpoint: "REJECT_CHECKPOINT", +} + +// String returns the RejectCode in human-readable form. +func (code RejectCode) String() string { + if s, ok := rejectCodeStrings[code]; ok { + return s + } + + return fmt.Sprintf("Unknown RejectCode (%d)", uint8(code)) +} + +// MsgReject implements the Message interface and represents a bitcoin reject +// message. +// +// This message was not added until protocol version RejectVersion. +type MsgReject struct { + // Cmd is the command for the message which was rejected such as + // as CmdBlock or CmdTx. This can be obtained from the Command function + // of a Message. + Cmd string + + // RejectCode is a code indicating why the command was rejected. It + // is encoded as a uint8 on the wire. + Code RejectCode + + // Reason is a human-readable string with specific details (over and + // above the reject code) about why the command was rejected. + Reason string + + // Hash identifies a specific block or transaction that was rejected + // and therefore only applies the MsgBlock and MsgTx messages. + Hash ShaHash +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgReject) BtcDecode(r io.Reader, pver uint32) error { + if pver < RejectVersion { + str := fmt.Sprintf("reject message invalid for protocol "+ + "version %d", pver) + return messageError("MsgReject.BtcDecode", str) + } + + // Command that was rejected. + cmd, err := readVarString(r, pver) + if err != nil { + return err + } + msg.Cmd = cmd + + // Code indicating why the command was rejected. + err = readElement(r, &msg.Code) + if err != nil { + return err + } + + // Human readable string with specific details (over and above the + // reject code above) about why the command was rejected. + reason, err := readVarString(r, pver) + if err != nil { + return err + } + msg.Reason = reason + + // 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) + if err != nil { + return err + } + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgReject) BtcEncode(w io.Writer, pver uint32) error { + if pver < RejectVersion { + str := fmt.Sprintf("reject message invalid for protocol "+ + "version %d", pver) + return messageError("MsgReject.BtcEncode", str) + } + + // Command that was rejected. + err := writeVarString(w, pver, msg.Cmd) + if err != nil { + return err + } + + // Code indicating why the command was rejected. + err = writeElement(w, msg.Code) + if err != nil { + 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) + if err != nil { + return err + } + + // 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) + if err != nil { + return err + } + } + + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgReject) Command() string { + return CmdReject +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgReject) MaxPayloadLength(pver uint32) uint32 { + plen := uint32(0) + // The reject message did not exist before protocol version + // RejectVersion. + if pver >= RejectVersion { + // Unfortunately the bitcoin protocol does not enforce a sane + // limit on the length of the reason, so the max payload is the + // overall maximum message payload. + plen = MaxMessagePayload + } + + return plen +} + +// NewMsgReject returns a new bitcoin reject message that conforms to the +// Message interface. See MsgReject for details. +func NewMsgReject(command string, code RejectCode, reason string) *MsgReject { + return &MsgReject{ + Cmd: command, + Code: code, + Reason: reason, + } +} diff --git a/wire/msgreject_test.go b/wire/msgreject_test.go new file mode 100644 index 00000000..8b7a400c --- /dev/null +++ b/wire/msgreject_test.go @@ -0,0 +1,385 @@ +// Copyright (c) 2014-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestRejectCodeStringer tests the stringized output for the reject code type. +func TestRejectCodeStringer(t *testing.T) { + tests := []struct { + in wire.RejectCode + want string + }{ + {wire.RejectMalformed, "REJECT_MALFORMED"}, + {wire.RejectInvalid, "REJECT_INVALID"}, + {wire.RejectObsolete, "REJECT_OBSOLETE"}, + {wire.RejectDuplicate, "REJECT_DUPLICATE"}, + {wire.RejectNonstandard, "REJECT_NONSTANDARD"}, + {wire.RejectDust, "REJECT_DUST"}, + {wire.RejectInsufficientFee, "REJECT_INSUFFICIENTFEE"}, + {wire.RejectCheckpoint, "REJECT_CHECKPOINT"}, + {0xff, "Unknown RejectCode (255)"}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result := test.in.String() + if result != test.want { + t.Errorf("String #%d\n got: %s want: %s", i, result, + test.want) + continue + } + } + +} + +// TestRejectLatest tests the MsgPong API against the latest protocol version. +func TestRejectLatest(t *testing.T) { + pver := wire.ProtocolVersion + + // Create reject message data. + rejCommand := (&wire.MsgBlock{}).Command() + rejCode := wire.RejectDuplicate + rejReason := "duplicate block" + rejHash := mainNetGenesisHash + + // Ensure we get the correct data back out. + msg := wire.NewMsgReject(rejCommand, rejCode, rejReason) + msg.Hash = rejHash + if msg.Cmd != rejCommand { + t.Errorf("NewMsgReject: wrong rejected command - got %v, "+ + "want %v", msg.Cmd, rejCommand) + } + if msg.Code != rejCode { + t.Errorf("NewMsgReject: wrong rejected code - got %v, "+ + "want %v", msg.Code, rejCode) + } + if msg.Reason != rejReason { + t.Errorf("NewMsgReject: wrong rejected reason - got %v, "+ + "want %v", msg.Reason, rejReason) + } + + // Ensure the command is expected value. + wantCmd := "reject" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgReject: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + wantPayload := uint32(wire.MaxMessagePayload) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Test encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("encode of MsgReject failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + readMsg := wire.MsgReject{} + err = readMsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgReject failed %v err <%v>", buf.Bytes(), + err) + } + + // Ensure decoded data is the same. + if msg.Cmd != readMsg.Cmd { + t.Errorf("Should get same reject command - got %v, want %v", + readMsg.Cmd, msg.Cmd) + } + if msg.Code != readMsg.Code { + t.Errorf("Should get same reject code - got %v, want %v", + readMsg.Code, msg.Code) + } + if msg.Reason != readMsg.Reason { + t.Errorf("Should get same reject reason - got %v, want %v", + readMsg.Reason, msg.Reason) + } + if msg.Hash != readMsg.Hash { + t.Errorf("Should get same reject hash - got %v, want %v", + readMsg.Hash, msg.Hash) + } +} + +// TestRejectBeforeAdded tests the MsgReject API against a protocol version +// before the version which introduced it (RejectVersion). +func TestRejectBeforeAdded(t *testing.T) { + // Use the protocol version just prior to RejectVersion. + pver := wire.RejectVersion - 1 + + // Create reject message data. + rejCommand := (&wire.MsgBlock{}).Command() + rejCode := wire.RejectDuplicate + rejReason := "duplicate block" + rejHash := mainNetGenesisHash + + msg := wire.NewMsgReject(rejCommand, rejCode, rejReason) + msg.Hash = rejHash + + // Ensure max payload is expected value for old protocol version. + size := msg.MaxPayloadLength(pver) + if size != 0 { + t.Errorf("Max length should be 0 for reject protocol version %d.", + pver) + } + + // Test encode with old protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, pver) + if err == nil { + t.Errorf("encode of MsgReject succeeded when it shouldn't "+ + "have %v", msg) + } + + // // Test decode with old protocol version. + readMsg := wire.MsgReject{} + err = readMsg.BtcDecode(&buf, pver) + if err == nil { + t.Errorf("decode of MsgReject succeeded when it shouldn't "+ + "have %v", spew.Sdump(buf.Bytes())) + } + + // Since this protocol version doesn't support reject, make sure various + // fields didn't get encoded and decoded back out. + if msg.Cmd == readMsg.Cmd { + t.Errorf("Should not get same reject command for protocol "+ + "version %d", pver) + } + if msg.Code == readMsg.Code { + t.Errorf("Should not get same reject code for protocol "+ + "version %d", pver) + } + if msg.Reason == readMsg.Reason { + t.Errorf("Should not get same reject reason for protocol "+ + "version %d", pver) + } + if msg.Hash == readMsg.Hash { + t.Errorf("Should not get same reject hash for protocol "+ + "version %d", pver) + } +} + +// TestRejectCrossProtocol tests the MsgReject API when encoding with the latest +// protocol version and decoded with a version before the version which +// introduced it (RejectVersion). +func TestRejectCrossProtocol(t *testing.T) { + // Create reject message data. + rejCommand := (&wire.MsgBlock{}).Command() + rejCode := wire.RejectDuplicate + rejReason := "duplicate block" + rejHash := mainNetGenesisHash + + msg := wire.NewMsgReject(rejCommand, rejCode, rejReason) + msg.Hash = rejHash + + // Encode with latest protocol version. + var buf bytes.Buffer + err := msg.BtcEncode(&buf, wire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgReject failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + readMsg := wire.MsgReject{} + err = readMsg.BtcDecode(&buf, wire.RejectVersion-1) + if err == nil { + t.Errorf("encode of MsgReject succeeded when it shouldn't "+ + "have %v", msg) + } + + // Since one of the protocol versions doesn't support the reject + // message, make sure the various fields didn't get encoded and decoded + // back out. + if msg.Cmd == readMsg.Cmd { + t.Errorf("Should not get same reject command for cross protocol") + } + if msg.Code == readMsg.Code { + t.Errorf("Should not get same reject code for cross protocol") + } + if msg.Reason == readMsg.Reason { + t.Errorf("Should not get same reject reason for cross protocol") + } + if msg.Hash == readMsg.Hash { + t.Errorf("Should not get same reject hash for cross protocol") + } +} + +// TestRejectWire tests the MsgReject wire encode and decode for various +// protocol versions. +func TestRejectWire(t *testing.T) { + tests := []struct { + msg wire.MsgReject // Message to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version rejected command version (no hash). + { + wire.MsgReject{ + Cmd: "version", + Code: wire.RejectDuplicate, + Reason: "duplicate version", + }, + []byte{ + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version" + 0x12, // wire.RejectDuplicate + 0x11, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, // "duplicate version" + }, + wire.ProtocolVersion, + }, + // Latest protocol version rejected command block (has hash). + { + wire.MsgReject{ + Cmd: "block", + Code: wire.RejectDuplicate, + Reason: "duplicate block", + Hash: mainNetGenesisHash, + }, + []byte{ + 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "block" + 0x12, // wire.RejectDuplicate + 0x0f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "duplicate block" + 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, // mainNetGenesisHash + }, + wire.ProtocolVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.msg.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgReject + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(msg, test.msg) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.msg)) + continue + } + } +} + +// TestRejectWireErrors performs negative tests against wire encode and decode +// of MsgReject to confirm error paths work correctly. +func TestRejectWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverNoReject := wire.RejectVersion - 1 + wireErr := &wire.MessageError{} + + baseReject := wire.NewMsgReject("block", wire.RejectDuplicate, + "duplicate block") + baseReject.Hash = mainNetGenesisHash + baseRejectEncoded := []byte{ + 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "block" + 0x12, // wire.RejectDuplicate + 0x0f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "duplicate block" + 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, // mainNetGenesisHash + } + + tests := []struct { + in *wire.MsgReject // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in reject command. + {baseReject, baseRejectEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in reject code. + {baseReject, baseRejectEncoded, pver, 6, io.ErrShortWrite, io.EOF}, + // Force error in reject reason. + {baseReject, baseRejectEncoded, pver, 7, io.ErrShortWrite, io.EOF}, + // Force error in reject hash. + {baseReject, baseRejectEncoded, pver, 23, io.ErrShortWrite, io.EOF}, + // Force error due to unsupported protocol version. + {baseReject, baseRejectEncoded, pverNoReject, 6, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgReject + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} diff --git a/wire/msgtx.go b/wire/msgtx.go new file mode 100644 index 00000000..19a0bc3e --- /dev/null +++ b/wire/msgtx.go @@ -0,0 +1,562 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "strconv" +) + +const ( + // TxVersion is the current latest supported transaction version. + TxVersion = 1 + + // MaxTxInSequenceNum is the maximum sequence number the sequence field + // of a transaction input can be. + MaxTxInSequenceNum uint32 = 0xffffffff + + // MaxPrevOutIndex is the maximum index the index field of a previous + // outpoint can be. + MaxPrevOutIndex uint32 = 0xffffffff +) + +// defaultTxInOutAlloc is the default size used for the backing array for +// transaction inputs and outputs. The array will dynamically grow as needed, +// but this figure is intended to provide enough space for the number of +// inputs and outputs in a typical transaction without needing to grow the +// backing array multiple times. +const defaultTxInOutAlloc = 15 + +const ( + // minTxInPayload is the minimum payload size for a transaction input. + // PreviousOutPoint.Hash + PreviousOutPoint.Index 4 bytes + Varint for + // SignatureScript length 1 byte + Sequence 4 bytes. + minTxInPayload = 9 + HashSize + + // maxTxInPerMessage is the maximum number of transactions inputs that + // a transaction which fits into a message could possibly have. + maxTxInPerMessage = (MaxMessagePayload / minTxInPayload) + 1 + + // minTxOutPayload is the minimum payload size for a transaction output. + // Value 8 bytes + Varint for PkScript length 1 byte. + minTxOutPayload = 9 + + // maxTxOutPerMessage is the maximum number of transactions outputs that + // a transaction which fits into a message could possibly have. + maxTxOutPerMessage = (MaxMessagePayload / minTxOutPayload) + 1 + + // minTxPayload is the minimum payload size for a transaction. Note + // that any realistically usable transaction must have at least one + // input or output, but that is a rule enforced at a higher layer, so + // it is intentionally not included here. + // Version 4 bytes + Varint number of transaction inputs 1 byte + Varint + // number of transaction outputs 1 byte + LockTime 4 bytes + min input + // payload + min output payload. + minTxPayload = 10 +) + +// OutPoint defines a bitcoin data type that is used to track previous +// transaction outputs. +type OutPoint struct { + Hash ShaHash + Index uint32 +} + +// NewOutPoint returns a new bitcoin transaction outpoint point with the +// provided hash and index. +func NewOutPoint(hash *ShaHash, index uint32) *OutPoint { + return &OutPoint{ + Hash: *hash, + Index: index, + } +} + +// String returns the OutPoint in the human-readable form "hash:index". +func (o OutPoint) String() string { + // Allocate enough for hash string, colon, and 10 digits. Although + // at the time of writing, the number of digits can be no greater than + // the length of the decimal representation of maxTxOutPerMessage, the + // maximum message payload may increase in the future and this + // optimization may go unnoticed, so allocate space for 10 decimal + // digits, which will fit any uint32. + buf := make([]byte, 2*HashSize+1, 2*HashSize+1+10) + copy(buf, o.Hash.String()) + buf[2*HashSize] = ':' + buf = strconv.AppendUint(buf, uint64(o.Index), 10) + return string(buf) +} + +// TxIn defines a bitcoin transaction input. +type TxIn struct { + PreviousOutPoint OutPoint + SignatureScript []byte + Sequence uint32 +} + +// SerializeSize returns the number of bytes it would take to serialize the +// the transaction input. +func (t *TxIn) SerializeSize() int { + // Outpoint Hash 32 bytes + Outpoint Index 4 bytes + Sequence 4 bytes + + // serialized varint size for the length of SignatureScript + + // SignatureScript bytes. + return 40 + VarIntSerializeSize(uint64(len(t.SignatureScript))) + + len(t.SignatureScript) +} + +// NewTxIn returns a new bitcoin transaction input with the provided +// previous outpoint point and signature script with a default sequence of +// MaxTxInSequenceNum. +func NewTxIn(prevOut *OutPoint, signatureScript []byte) *TxIn { + return &TxIn{ + PreviousOutPoint: *prevOut, + SignatureScript: signatureScript, + Sequence: MaxTxInSequenceNum, + } +} + +// TxOut defines a bitcoin transaction output. +type TxOut struct { + Value int64 + PkScript []byte +} + +// SerializeSize returns the number of bytes it would take to serialize the +// the transaction output. +func (t *TxOut) SerializeSize() int { + // Value 8 bytes + serialized varint size for the length of PkScript + + // PkScript bytes. + return 8 + VarIntSerializeSize(uint64(len(t.PkScript))) + len(t.PkScript) +} + +// NewTxOut returns a new bitcoin transaction output with the provided +// transaction value and public key script. +func NewTxOut(value int64, pkScript []byte) *TxOut { + return &TxOut{ + Value: value, + PkScript: pkScript, + } +} + +// MsgTx implements the Message interface and represents a bitcoin tx message. +// It is used to deliver transaction information in response to a getdata +// message (MsgGetData) for a given transaction. +// +// Use the AddTxIn and AddTxOut functions to build up the list of transaction +// inputs and outputs. +type MsgTx struct { + Version int32 + TxIn []*TxIn + TxOut []*TxOut + LockTime uint32 +} + +// AddTxIn adds a transaction input to the message. +func (msg *MsgTx) AddTxIn(ti *TxIn) { + msg.TxIn = append(msg.TxIn, ti) +} + +// AddTxOut adds a transaction output to the message. +func (msg *MsgTx) AddTxOut(to *TxOut) { + msg.TxOut = append(msg.TxOut, to) +} + +// TxSha generates the ShaHash name for the transaction. +func (msg *MsgTx) TxSha() (ShaHash, error) { + // Encode the transaction and calculate double sha256 on the result. + // Ignore the error returns since the only way the encode could fail + // is being out of memory or due to nil pointers, both of which would + // cause a run-time panic. Also, SetBytes can't fail here due to the + // fact DoubleSha256 always returns a []byte of the right size + // regardless of input. + buf := bytes.NewBuffer(make([]byte, 0, msg.SerializeSize())) + _ = msg.Serialize(buf) + var sha ShaHash + _ = sha.SetBytes(DoubleSha256(buf.Bytes())) + + // Even though this function can't currently fail, it still returns + // a potential error to help future proof the API should a failure + // become possible. + return sha, nil +} + +// Copy creates a deep copy of a transaction so that the original does not get +// modified when the copy is manipulated. +func (msg *MsgTx) Copy() *MsgTx { + // Create new tx and start by copying primitive values and making space + // for the transaction inputs and outputs. + newTx := MsgTx{ + Version: msg.Version, + TxIn: make([]*TxIn, 0, len(msg.TxIn)), + TxOut: make([]*TxOut, 0, len(msg.TxOut)), + LockTime: msg.LockTime, + } + + // Deep copy the old TxIn data. + for _, oldTxIn := range msg.TxIn { + // Deep copy the old previous outpoint. + oldOutPoint := oldTxIn.PreviousOutPoint + newOutPoint := OutPoint{} + newOutPoint.Hash.SetBytes(oldOutPoint.Hash[:]) + newOutPoint.Index = oldOutPoint.Index + + // Deep copy the old signature script. + var newScript []byte + oldScript := oldTxIn.SignatureScript + oldScriptLen := len(oldScript) + if oldScriptLen > 0 { + newScript = make([]byte, oldScriptLen, oldScriptLen) + copy(newScript, oldScript[:oldScriptLen]) + } + + // Create new txIn with the deep copied data and append it to + // new Tx. + newTxIn := TxIn{ + PreviousOutPoint: newOutPoint, + SignatureScript: newScript, + Sequence: oldTxIn.Sequence, + } + newTx.TxIn = append(newTx.TxIn, &newTxIn) + } + + // Deep copy the old TxOut data. + for _, oldTxOut := range msg.TxOut { + // Deep copy the old PkScript + var newScript []byte + oldScript := oldTxOut.PkScript + oldScriptLen := len(oldScript) + if oldScriptLen > 0 { + newScript = make([]byte, oldScriptLen, oldScriptLen) + copy(newScript, oldScript[:oldScriptLen]) + } + + // Create new txOut with the deep copied data and append it to + // new Tx. + newTxOut := TxOut{ + Value: oldTxOut.Value, + PkScript: newScript, + } + newTx.TxOut = append(newTx.TxOut, &newTxOut) + } + + return &newTx +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +// See Deserialize for decoding transactions stored to disk, such as in a +// database, as opposed to decoding transactions from the wire. +func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { + var buf [4]byte + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return err + } + msg.Version = int32(binary.LittleEndian.Uint32(buf[:])) + + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + // Prevent more input transactions than could possibly fit into a + // message. It would be possible to cause memory exhaustion and panics + // without a sane upper bound on this count. + if count > uint64(maxTxInPerMessage) { + str := fmt.Sprintf("too many input transactions to fit into "+ + "max message size [count %d, max %d]", count, + maxTxInPerMessage) + return messageError("MsgTx.BtcDecode", str) + } + + msg.TxIn = make([]*TxIn, count) + for i := uint64(0); i < count; i++ { + ti := TxIn{} + err = readTxIn(r, pver, msg.Version, &ti) + if err != nil { + return err + } + msg.TxIn[i] = &ti + } + + count, err = readVarInt(r, pver) + if err != nil { + return err + } + + // Prevent more output transactions than could possibly fit into a + // message. It would be possible to cause memory exhaustion and panics + // without a sane upper bound on this count. + if count > uint64(maxTxOutPerMessage) { + str := fmt.Sprintf("too many output transactions to fit into "+ + "max message size [count %d, max %d]", count, + maxTxOutPerMessage) + return messageError("MsgTx.BtcDecode", str) + } + + msg.TxOut = make([]*TxOut, count) + for i := uint64(0); i < count; i++ { + to := TxOut{} + err = readTxOut(r, pver, msg.Version, &to) + if err != nil { + return err + } + msg.TxOut[i] = &to + } + + _, err = io.ReadFull(r, buf[:]) + if err != nil { + return err + } + msg.LockTime = binary.LittleEndian.Uint32(buf[:]) + + return nil +} + +// Deserialize decodes a transaction from r into the receiver using a format +// that is suitable for long-term storage such as a database while respecting +// the Version field in the transaction. This function differs from BtcDecode +// in that BtcDecode decodes from the bitcoin wire protocol as it was sent +// across the network. The wire encoding can technically differ depending on +// the protocol version and doesn't even really need to match the format of a +// stored transaction at all. As of the time this comment was written, the +// encoded transaction is the same in both instances, but there is a distinct +// difference and separating the two allows the API to be flexible enough to +// deal with changes. +func (msg *MsgTx) Deserialize(r io.Reader) error { + // 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 BtcDecode. + return msg.BtcDecode(r, 0) +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +// See Serialize for encoding transactions to be stored to disk, such as in a +// database, as opposed to encoding transactions for the wire. +func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error { + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], uint32(msg.Version)) + _, err := w.Write(buf[:]) + if err != nil { + return err + } + + count := uint64(len(msg.TxIn)) + err = writeVarInt(w, pver, count) + if err != nil { + return err + } + + for _, ti := range msg.TxIn { + err = writeTxIn(w, pver, msg.Version, ti) + if err != nil { + return err + } + } + + count = uint64(len(msg.TxOut)) + err = writeVarInt(w, pver, count) + if err != nil { + return err + } + + for _, to := range msg.TxOut { + err = writeTxOut(w, pver, msg.Version, to) + if err != nil { + return err + } + } + + binary.LittleEndian.PutUint32(buf[:], msg.LockTime) + _, err = w.Write(buf[:]) + if err != nil { + return err + } + + return nil +} + +// Serialize encodes the transaction to w using a format that suitable for +// long-term storage such as a database while respecting the Version field in +// the transaction. This function differs from BtcEncode in that BtcEncode +// encodes the transaction to the bitcoin wire protocol in order to be sent +// across the network. The wire encoding can technically differ depending on +// the protocol version and doesn't even really need to match the format of a +// stored transaction at all. As of the time this comment was written, the +// encoded transaction is the same in both instances, but there is a distinct +// difference and separating the two allows the API to be flexible enough to +// deal with changes. +func (msg *MsgTx) Serialize(w io.Writer) error { + // 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 BtcEncode. + return msg.BtcEncode(w, 0) + +} + +// SerializeSize returns the number of bytes it would take to serialize the +// the transaction. +func (msg *MsgTx) SerializeSize() int { + // Version 4 bytes + LockTime 4 bytes + Serialized varint size for the + // number of transaction inputs and outputs. + n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) + + VarIntSerializeSize(uint64(len(msg.TxOut))) + + for _, txIn := range msg.TxIn { + n += txIn.SerializeSize() + } + + for _, txOut := range msg.TxOut { + n += txOut.SerializeSize() + } + + return n +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgTx) Command() string { + return CmdTx +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 { + return MaxBlockPayload +} + +// NewMsgTx returns a new bitcoin tx message that conforms to the Message +// interface. The return instance has a default version of TxVersion and there +// are no transaction inputs or outputs. Also, the lock time is set to zero +// to indicate the transaction is valid immediately as opposed to some time in +// future. +func NewMsgTx() *MsgTx { + return &MsgTx{ + Version: TxVersion, + TxIn: make([]*TxIn, 0, defaultTxInOutAlloc), + TxOut: make([]*TxOut, 0, defaultTxInOutAlloc), + } +} + +// readOutPoint reads the next sequence of bytes from r as an OutPoint. +func readOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error { + _, err := io.ReadFull(r, op.Hash[:]) + if err != nil { + return err + } + + var buf [4]byte + _, err = io.ReadFull(r, buf[:]) + if err != nil { + return err + } + op.Index = binary.LittleEndian.Uint32(buf[:]) + 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 { + _, err := w.Write(op.Hash[:]) + if err != nil { + return err + } + + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], op.Index) + _, err = w.Write(buf[:]) + if err != nil { + return err + } + return nil +} + +// readTxIn 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 { + var op OutPoint + err := readOutPoint(r, pver, version, &op) + if err != nil { + return err + } + ti.PreviousOutPoint = op + + ti.SignatureScript, err = readVarBytes(r, pver, MaxMessagePayload, + "transaction input signature script") + if err != nil { + return err + } + + var buf [4]byte + _, err = io.ReadFull(r, buf[:]) + if err != nil { + return err + } + ti.Sequence = binary.LittleEndian.Uint32(buf[:]) + + 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) + if err != nil { + return err + } + + err = writeVarBytes(w, pver, ti.SignatureScript) + if err != nil { + return err + } + + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], ti.Sequence) + _, err = w.Write(buf[:]) + if err != nil { + return err + } + + return nil +} + +// readTxOut reads the next sequence of bytes from r as a transaction output +// (TxOut). +func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error { + var buf [8]byte + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return err + } + to.Value = int64(binary.LittleEndian.Uint64(buf[:])) + + to.PkScript, err = readVarBytes(r, pver, MaxMessagePayload, + "transaction output public key script") + if err != nil { + return err + } + + return nil +} + +// writeTxOut encodes to into the bitcoin protocol encoding for a transaction +// output (TxOut) to w. +func writeTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error { + var buf [8]byte + binary.LittleEndian.PutUint64(buf[:], uint64(to.Value)) + _, err := w.Write(buf[:]) + if err != nil { + return err + } + + err = writeVarBytes(w, pver, to.PkScript) + if err != nil { + return err + } + return nil +} diff --git a/wire/msgtx_test.go b/wire/msgtx_test.go new file mode 100644 index 00000000..d165d03d --- /dev/null +++ b/wire/msgtx_test.go @@ -0,0 +1,692 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestTx tests the MsgTx API. +func TestTx(t *testing.T) { + pver := wire.ProtocolVersion + + // Block 100000 hash. + hashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the command is expected value. + wantCmd := "tx" + msg := wire.NewMsgTx() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgAddr: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value for latest protocol version. + // Num addresses (varInt) + max allowed addresses. + wantPayload := uint32(1000 * 1000) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure we get the same transaction output point data back out. + // NOTE: This is a block hash and made up index, but we're only + // testing package functionality. + prevOutIndex := uint32(1) + prevOut := wire.NewOutPoint(hash, prevOutIndex) + if !prevOut.Hash.IsEqual(hash) { + t.Errorf("NewOutPoint: wrong hash - got %v, want %v", + spew.Sprint(&prevOut.Hash), spew.Sprint(hash)) + } + if prevOut.Index != prevOutIndex { + t.Errorf("NewOutPoint: wrong index - got %v, want %v", + prevOut.Index, prevOutIndex) + } + prevOutStr := fmt.Sprintf("%s:%d", hash.String(), prevOutIndex) + if s := prevOut.String(); s != prevOutStr { + t.Errorf("OutPoint.String: unexpected result - got %v, "+ + "want %v", s, prevOutStr) + } + + // Ensure we get the same transaction input back out. + sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62} + txIn := wire.NewTxIn(prevOut, sigScript) + if !reflect.DeepEqual(&txIn.PreviousOutPoint, prevOut) { + t.Errorf("NewTxIn: wrong prev outpoint - got %v, want %v", + spew.Sprint(&txIn.PreviousOutPoint), + spew.Sprint(prevOut)) + } + if !bytes.Equal(txIn.SignatureScript, sigScript) { + t.Errorf("NewTxIn: wrong signature script - got %v, want %v", + spew.Sdump(txIn.SignatureScript), + spew.Sdump(sigScript)) + } + + // Ensure we get the same transaction output back out. + txValue := int64(5000000000) + pkScript := []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + } + txOut := wire.NewTxOut(txValue, pkScript) + if txOut.Value != txValue { + t.Errorf("NewTxOut: wrong pk script - got %v, want %v", + txOut.Value, txValue) + + } + if !bytes.Equal(txOut.PkScript, pkScript) { + t.Errorf("NewTxOut: wrong pk script - got %v, want %v", + spew.Sdump(txOut.PkScript), + spew.Sdump(pkScript)) + } + + // Ensure transaction inputs are added properly. + msg.AddTxIn(txIn) + if !reflect.DeepEqual(msg.TxIn[0], txIn) { + t.Errorf("AddTxIn: wrong transaction input added - got %v, want %v", + spew.Sprint(msg.TxIn[0]), spew.Sprint(txIn)) + } + + // Ensure transaction outputs are added properly. + msg.AddTxOut(txOut) + if !reflect.DeepEqual(msg.TxOut[0], txOut) { + t.Errorf("AddTxIn: wrong transaction output added - got %v, want %v", + spew.Sprint(msg.TxOut[0]), spew.Sprint(txOut)) + } + + // Ensure the copy produced an identical transaction message. + newMsg := msg.Copy() + if !reflect.DeepEqual(newMsg, msg) { + t.Errorf("Copy: mismatched tx messages - got %v, want %v", + spew.Sdump(newMsg), spew.Sdump(msg)) + } + + return +} + +// TestTxSha tests the ability to generate the hash of a transaction accurately. +func TestTxSha(t *testing.T) { + // Hash of first transaction from block 113875. + hashStr := "f051e59b5e2503ac626d03aaeac8ab7be2d72ba4b7e97119c5852d70d52dcb86" + wantHash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + return + } + + // First transaction from block 113875. + msgTx := wire.NewMsgTx() + txIn := wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}, + Sequence: 0xffffffff, + } + txOut := wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + } + msgTx.AddTxIn(&txIn) + msgTx.AddTxOut(&txOut) + msgTx.LockTime = 0 + + // Ensure the hash produced is expected. + txHash, err := msgTx.TxSha() + if err != nil { + t.Errorf("TxSha: %v", err) + } + if !txHash.IsEqual(wantHash) { + t.Errorf("TxSha: wrong hash - got %v, want %v", + spew.Sprint(txHash), spew.Sprint(wantHash)) + } +} + +// TestTxWire tests the MsgTx wire encode and decode for various numbers +// of transaction inputs and outputs and protocol versions. +func TestTxWire(t *testing.T) { + // Empty tx message. + noTx := wire.NewMsgTx() + noTx.Version = 1 + noTxEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, // Version + 0x00, // Varint for number of input transactions + 0x00, // Varint for number of output transactions + 0x00, 0x00, 0x00, 0x00, // Lock time + } + + tests := []struct { + in *wire.MsgTx // Message to encode + out *wire.MsgTx // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no transactions. + { + noTx, + noTx, + noTxEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no transactions. + { + noTx, + noTx, + noTxEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no transactions. + { + noTx, + noTx, + noTxEncoded, + wire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no transactions. + { + noTx, + noTx, + noTxEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no transactions. + { + noTx, + noTx, + noTxEncoded, + wire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgTx + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(&msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestTxWireErrors performs negative tests against wire encode and decode +// of MsgTx to confirm error paths work correctly. +func TestTxWireErrors(t *testing.T) { + // Use protocol version 60002 specifically here instead of the latest + // because the test data is using bytes encoded with that protocol + // version. + pver := uint32(60002) + + tests := []struct { + in *wire.MsgTx // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in version. + {multiTx, multiTxEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in number of transaction inputs. + {multiTx, multiTxEncoded, pver, 4, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block hash. + {multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block hash. + {multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block output index. + {multiTx, multiTxEncoded, pver, 37, io.ErrShortWrite, io.EOF}, + // Force error in transaction input signature script length. + {multiTx, multiTxEncoded, pver, 41, io.ErrShortWrite, io.EOF}, + // Force error in transaction input signature script. + {multiTx, multiTxEncoded, pver, 42, io.ErrShortWrite, io.EOF}, + // Force error in transaction input sequence. + {multiTx, multiTxEncoded, pver, 49, io.ErrShortWrite, io.EOF}, + // Force error in number of transaction outputs. + {multiTx, multiTxEncoded, pver, 53, io.ErrShortWrite, io.EOF}, + // Force error in transaction output value. + {multiTx, multiTxEncoded, pver, 54, io.ErrShortWrite, io.EOF}, + // Force error in transaction output pk script length. + {multiTx, multiTxEncoded, pver, 62, io.ErrShortWrite, io.EOF}, + // Force error in transaction output pk script. + {multiTx, multiTxEncoded, pver, 63, io.ErrShortWrite, io.EOF}, + // Force error in transaction output lock time. + {multiTx, multiTxEncoded, pver, 130, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + var msg wire.MsgTx + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestTxSerialize tests MsgTx serialize and deserialize. +func TestTxSerialize(t *testing.T) { + noTx := wire.NewMsgTx() + noTx.Version = 1 + noTxEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, // Version + 0x00, // Varint for number of input transactions + 0x00, // Varint for number of output transactions + 0x00, 0x00, 0x00, 0x00, // Lock time + } + + tests := []struct { + in *wire.MsgTx // Message to encode + out *wire.MsgTx // Expected decoded message + buf []byte // Serialized data + }{ + // No transactions. + { + noTx, + noTx, + noTxEncoded, + }, + + // Multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Serialize the transaction. + var buf bytes.Buffer + err := test.in.Serialize(&buf) + if err != nil { + t.Errorf("Serialize #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("Serialize #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Deserialize the transaction. + var tx wire.MsgTx + rbuf := bytes.NewReader(test.buf) + err = tx.Deserialize(rbuf) + if err != nil { + t.Errorf("Deserialize #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&tx, test.out) { + t.Errorf("Deserialize #%d\n got: %s want: %s", i, + spew.Sdump(&tx), spew.Sdump(test.out)) + continue + } + } +} + +// TestTxSerializeErrors performs negative tests against wire encode and decode +// of MsgTx to confirm error paths work correctly. +func TestTxSerializeErrors(t *testing.T) { + tests := []struct { + in *wire.MsgTx // Value to encode + buf []byte // Serialized data + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in version. + {multiTx, multiTxEncoded, 0, io.ErrShortWrite, io.EOF}, + // Force error in number of transaction inputs. + {multiTx, multiTxEncoded, 4, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block hash. + {multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block hash. + {multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF}, + // Force error in transaction input previous block output index. + {multiTx, multiTxEncoded, 37, io.ErrShortWrite, io.EOF}, + // Force error in transaction input signature script length. + {multiTx, multiTxEncoded, 41, io.ErrShortWrite, io.EOF}, + // Force error in transaction input signature script. + {multiTx, multiTxEncoded, 42, io.ErrShortWrite, io.EOF}, + // Force error in transaction input sequence. + {multiTx, multiTxEncoded, 49, io.ErrShortWrite, io.EOF}, + // Force error in number of transaction outputs. + {multiTx, multiTxEncoded, 53, io.ErrShortWrite, io.EOF}, + // Force error in transaction output value. + {multiTx, multiTxEncoded, 54, io.ErrShortWrite, io.EOF}, + // Force error in transaction output pk script length. + {multiTx, multiTxEncoded, 62, io.ErrShortWrite, io.EOF}, + // Force error in transaction output pk script. + {multiTx, multiTxEncoded, 63, io.ErrShortWrite, io.EOF}, + // Force error in transaction output lock time. + {multiTx, multiTxEncoded, 130, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Serialize the transaction. + w := newFixedWriter(test.max) + err := test.in.Serialize(w) + if err != test.writeErr { + t.Errorf("Serialize #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Deserialize the transaction. + var tx wire.MsgTx + r := newFixedReader(test.max, test.buf) + err = tx.Deserialize(r) + if err != test.readErr { + t.Errorf("Deserialize #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} + +// TestTxOverflowErrors performs tests to ensure deserializing transactions +// which are intentionally crafted to use large values for the variable number +// of inputs and outputs are handled properly. This could otherwise potentially +// be used as an attack vector. +func TestTxOverflowErrors(t *testing.T) { + // Use protocol version 70001 and transaction version 1 specifically + // here instead of the latest values because the test data is using + // bytes encoded with those versions. + pver := uint32(70001) + txVer := uint32(1) + + tests := []struct { + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + version uint32 // Transaction version + err error // Expected error + }{ + // Transaction that claims to have ~uint64(0) inputs. + { + []byte{ + 0x00, 0x00, 0x00, 0x01, // Version + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // Varint for number of input transactions + }, pver, txVer, &wire.MessageError{}, + }, + + // Transaction that claims to have ~uint64(0) outputs. + { + []byte{ + 0x00, 0x00, 0x00, 0x01, // Version + 0x00, // Varint for number of input transactions + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // Varint for number of output transactions + }, pver, txVer, &wire.MessageError{}, + }, + + // Transaction that has an input with a signature script that + // claims to have ~uint64(0) length. + { + []byte{ + 0x00, 0x00, 0x00, 0x01, // 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 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // Varint for length of signature script + }, pver, txVer, &wire.MessageError{}, + }, + + // Transaction that has an output with a public key script + // that claims to have ~uint64(0) length. + { + []byte{ + 0x00, 0x00, 0x00, 0x01, // 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 + 0x00, // Varint for length of signature script + 0xff, 0xff, 0xff, 0xff, // Sequence + 0x01, // Varint for number of output transactions + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transaction amount + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, // Varint for length of public key script + }, pver, txVer, &wire.MessageError{}, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Decode from wire format. + var msg wire.MsgTx + r := bytes.NewReader(test.buf) + err := msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, reflect.TypeOf(test.err)) + continue + } + + // Decode from wire format. + r = bytes.NewReader(test.buf) + err = msg.Deserialize(r) + if reflect.TypeOf(err) != reflect.TypeOf(test.err) { + t.Errorf("Deserialize #%d wrong error got: %v, want: %v", + i, err, reflect.TypeOf(test.err)) + continue + } + } +} + +// TestTxSerializeSize performs tests to ensure the serialize size for various +// transactions is accurate. +func TestTxSerializeSize(t *testing.T) { + // Empty tx message. + noTx := wire.NewMsgTx() + noTx.Version = 1 + + tests := []struct { + in *wire.MsgTx // Tx to encode + size int // Expected serialized size + }{ + // No inputs or outpus. + {noTx, 10}, + + // Transcaction with an input and an output. + {multiTx, 134}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + serializedSize := test.in.SerializeSize() + if serializedSize != test.size { + t.Errorf("MsgTx.SerializeSize: #%d got: %d, want: %d", i, + serializedSize, test.size) + continue + } + } +} + +// multiTx is a MsgTx with an input and output and used in various tests. +var multiTx = &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x12a05f200, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, +} + +// multiTxEncoded is the wire encoded bytes for multiTx using protocol version +// 60002 and is used in the various tests. +var multiTxEncoded = []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, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // 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, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + 0x00, 0x00, 0x00, 0x00, // Lock time +} diff --git a/wire/msgverack.go b/wire/msgverack.go new file mode 100644 index 00000000..95214352 --- /dev/null +++ b/wire/msgverack.go @@ -0,0 +1,46 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "io" +) + +// MsgVerAck defines a bitcoin verack message which is used for a peer to +// acknowledge a version message (MsgVersion) after it has used the information +// to negotiate parameters. It implements the Message interface. +// +// This message has no payload. +type MsgVerAck struct{} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgVerAck) BtcDecode(r io.Reader, pver uint32) error { + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgVerAck) BtcEncode(w io.Writer, pver uint32) error { + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgVerAck) Command() string { + return CmdVerAck +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgVerAck) MaxPayloadLength(pver uint32) uint32 { + return 0 +} + +// NewMsgVerAck returns a new bitcoin verack message that conforms to the +// Message interface. +func NewMsgVerAck() *MsgVerAck { + return &MsgVerAck{} +} diff --git a/wire/msgverack_test.go b/wire/msgverack_test.go new file mode 100644 index 00000000..b6dee460 --- /dev/null +++ b/wire/msgverack_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "reflect" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestVerAck tests the MsgVerAck API. +func TestVerAck(t *testing.T) { + pver := wire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "verack" + msg := wire.NewMsgVerAck() + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgVerAck: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value. + wantPayload := uint32(0) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + return +} + +// TestVerAckWire tests the MsgVerAck wire encode and decode for various +// protocol versions. +func TestVerAckWire(t *testing.T) { + msgVerAck := wire.NewMsgVerAck() + msgVerAckEncoded := []byte{} + + tests := []struct { + in *wire.MsgVerAck // Message to encode + out *wire.MsgVerAck // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgVerAck + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} diff --git a/wire/msgversion.go b/wire/msgversion.go new file mode 100644 index 00000000..f62f42c6 --- /dev/null +++ b/wire/msgversion.go @@ -0,0 +1,296 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "fmt" + "io" + "net" + "strings" + "time" +) + +// MaxUserAgentLen is the maximum allowed length for the user agent field in a +// version message (MsgVersion). +const MaxUserAgentLen = 2000 + +// DefaultUserAgent for wire in the stack +const DefaultUserAgent = "/btcwire:0.2.0/" + + +// MsgVersion implements the Message interface and represents a bitcoin version +// message. It is used for a peer to advertise itself as soon as an outbound +// connection is made. The remote peer then uses this information along with +// its own to negotiate. The remote peer must then respond with a version +// message of its own containing the negotiated values followed by a verack +// message (MsgVerAck). This exchange must take place before any further +// communication is allowed to proceed. +type MsgVersion struct { + // Version of the protocol the node is using. + ProtocolVersion int32 + + // Bitfield which identifies the enabled services. + Services ServiceFlag + + // Time the message was generated. This is encoded as an int64 on the wire. + Timestamp time.Time + + // Address of the remote peer. + AddrYou NetAddress + + // Address of the local peer. + AddrMe NetAddress + + // Unique value associated with message that is used to detect self + // connections. + Nonce uint64 + + // The user agent that generated messsage. This is a encoded as a varString + // on the wire. This has a max length of MaxUserAgentLen. + UserAgent string + + // Last block seen by the generator of the version message. + LastBlock int32 + + // Don't announce transactions to peer. + DisableRelayTx bool +} + +// HasService returns whether the specified service is supported by the peer +// that generated the message. +func (msg *MsgVersion) HasService(service ServiceFlag) bool { + if msg.Services&service == service { + return true + } + return false +} + +// AddService adds service as a supported service by the peer generating the +// message. +func (msg *MsgVersion) AddService(service ServiceFlag) { + msg.Services |= service +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// The version message is special in that the protocol version hasn't been +// negotiated yet. As a result, the pver field is ignored and any fields which +// are added in new versions are optional. This also mean that r must be a +// *bytes.Buffer so the number of remaining bytes can be ascertained. +// +// This is part of the Message interface implementation. +func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error { + buf, ok := r.(*bytes.Buffer) + if !ok { + return fmt.Errorf("MsgVersion.BtcDecode reader is not a " + + "*bytes.Buffer") + } + + var sec int64 + err := readElements(buf, &msg.ProtocolVersion, &msg.Services, &sec) + if err != nil { + return err + } + msg.Timestamp = time.Unix(sec, 0) + + err = readNetAddress(buf, pver, &msg.AddrYou, false) + if err != nil { + return err + } + + // Protocol versions >= 106 added a from address, nonce, and user agent + // field and they are only considered present if there are bytes + // remaining in the message. + if buf.Len() > 0 { + err = readNetAddress(buf, pver, &msg.AddrMe, false) + if err != nil { + return err + } + } + if buf.Len() > 0 { + err = readElement(buf, &msg.Nonce) + if err != nil { + return err + } + } + if buf.Len() > 0 { + userAgent, err := readVarString(buf, pver) + if err != nil { + return err + } + err = validateUserAgent(userAgent) + if err != nil { + return err + } + msg.UserAgent = userAgent + } + + // Protocol versions >= 209 added a last known block field. It is only + // considered present if there are bytes remaining in the message. + if buf.Len() > 0 { + err = readElement(buf, &msg.LastBlock) + if err != nil { + return err + } + } + + // There was no relay transactions field before BIP0037Version, but + // the default behavior prior to the addition of the field was to always + // relay transactions. + if buf.Len() > 0 { + // It's safe to ignore the error here since the buffer has at + // least one byte and that byte will result in a boolean value + // regardless of its value. Also, the wire encoding for the + // field is true when transactions should be relayed, so reverse + // it for the DisableRelayTx field. + var relayTx bool + readElement(r, &relayTx) + msg.DisableRelayTx = !relayTx + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error { + err := validateUserAgent(msg.UserAgent) + if err != nil { + return err + } + + err = writeElements(w, msg.ProtocolVersion, msg.Services, + msg.Timestamp.Unix()) + if err != nil { + return err + } + + err = writeNetAddress(w, pver, &msg.AddrYou, false) + if err != nil { + return err + } + + err = writeNetAddress(w, pver, &msg.AddrMe, false) + if err != nil { + return err + } + + err = writeElement(w, msg.Nonce) + if err != nil { + return err + } + + err = writeVarString(w, pver, msg.UserAgent) + if err != nil { + return err + } + + err = writeElement(w, msg.LastBlock) + if err != nil { + return err + } + + // There was no relay transactions field before BIP0037Version. Also, + // the wire encoding for the field is true when transactions should be + // relayed, so reverse it from the DisableRelayTx field. + if pver >= BIP0037Version { + err = writeElement(w, !msg.DisableRelayTx) + if err != nil { + return err + } + } + return nil +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgVersion) Command() string { + return CmdVersion +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 { + // XXX: <= 106 different + + // Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes + + // remote and local net addresses + nonce 8 bytes + length of user + // agent (varInt) + max allowed useragent length + last block 4 bytes + + // relay transactions flag 1 byte. + return 33 + (maxNetAddressPayload(pver) * 2) + MaxVarIntPayload + + MaxUserAgentLen +} + +// NewMsgVersion returns a new bitcoin version message that conforms to the +// Message interface using the passed parameters and defaults for the remaining +// fields. +func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64, + lastBlock int32) *MsgVersion { + + // Limit the timestamp to one second precision since the protocol + // doesn't support better. + return &MsgVersion{ + ProtocolVersion: int32(ProtocolVersion), + Services: 0, + Timestamp: time.Unix(time.Now().Unix(), 0), + AddrYou: *you, + AddrMe: *me, + Nonce: nonce, + UserAgent: DefaultUserAgent, + LastBlock: lastBlock, + DisableRelayTx: false, + } +} + +// NewMsgVersionFromConn is a convenience function that extracts the remote +// and local address from conn and returns a new bitcoin version message that +// conforms to the Message interface. See NewMsgVersion. +func NewMsgVersionFromConn(conn net.Conn, nonce uint64, + lastBlock int32) (*MsgVersion, error) { + + // Don't assume any services until we know otherwise. + lna, err := NewNetAddress(conn.LocalAddr(), 0) + if err != nil { + return nil, err + } + + // Don't assume any services until we know otherwise. + rna, err := NewNetAddress(conn.RemoteAddr(), 0) + if err != nil { + return nil, err + } + + return NewMsgVersion(lna, rna, nonce, lastBlock), nil +} + +// validateUserAgent checks userAgent length against MaxUserAgentLen +func validateUserAgent(userAgent string) error { + if len(userAgent) > MaxUserAgentLen { + str := fmt.Sprintf("user agent too long [len %v, max %v]", + len(userAgent), MaxUserAgentLen) + return messageError("MsgVersion", str) + } + return nil +} + +// AddUserAgent adds a user agent to the user agent string for the version +// message. The version string is not defined to any strict format, although +// it is recommended to use the form "major.minor.revision" e.g. "2.6.41". +func (msg *MsgVersion) AddUserAgent(name string, version string, + comments ...string) error { + + newUserAgent := fmt.Sprintf("%s:%s", name, version) + if len(comments) != 0 { + newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent, + strings.Join(comments, "; ")) + } + newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent) + err := validateUserAgent(newUserAgent) + if err != nil { + return err + } + msg.UserAgent = newUserAgent + return nil +} diff --git a/wire/msgversion_test.go b/wire/msgversion_test.go new file mode 100644 index 00000000..3507f1e2 --- /dev/null +++ b/wire/msgversion_test.go @@ -0,0 +1,596 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "net" + "reflect" + "strings" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestVersion tests the MsgVersion API. +func TestVersion(t *testing.T) { + pver := wire.ProtocolVersion + + // Create version message data. + lastBlock := int32(234234) + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} + me, err := wire.NewNetAddress(tcpAddrMe, wire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + tcpAddrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} + you, err := wire.NewNetAddress(tcpAddrYou, wire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + nonce, err := wire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: error generating nonce: %v", err) + } + + // Ensure we get the correct data back out. + msg := wire.NewMsgVersion(me, you, nonce, lastBlock) + if msg.ProtocolVersion != int32(pver) { + t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v", + msg.ProtocolVersion, pver) + } + if !reflect.DeepEqual(&msg.AddrMe, me) { + t.Errorf("NewMsgVersion: wrong me address - got %v, want %v", + spew.Sdump(&msg.AddrMe), spew.Sdump(me)) + } + if !reflect.DeepEqual(&msg.AddrYou, you) { + t.Errorf("NewMsgVersion: wrong you address - got %v, want %v", + spew.Sdump(&msg.AddrYou), spew.Sdump(you)) + } + if msg.Nonce != nonce { + t.Errorf("NewMsgVersion: wrong nonce - got %v, want %v", + msg.Nonce, nonce) + } + if msg.UserAgent != wire.DefaultUserAgent { + t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v", + msg.UserAgent, wire.DefaultUserAgent) + } + if msg.LastBlock != lastBlock { + t.Errorf("NewMsgVersion: wrong last block - got %v, want %v", + msg.LastBlock, lastBlock) + } + if msg.DisableRelayTx != false { + t.Errorf("NewMsgVersion: disable relay tx is not false by "+ + "default - got %v, want %v", msg.DisableRelayTx, false) + } + + msg.AddUserAgent("myclient", "1.2.3", "optional", "comments") + customUserAgent := wire.DefaultUserAgent + "myclient:1.2.3(optional; comments)/" + if msg.UserAgent != customUserAgent { + t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", + msg.UserAgent, customUserAgent) + } + + msg.AddUserAgent("mygui", "3.4.5") + customUserAgent += "mygui:3.4.5/" + if msg.UserAgent != customUserAgent { + t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", + msg.UserAgent, customUserAgent) + } + + // accounting for ":", "/" + err = msg.AddUserAgent(strings.Repeat("t", + wire.MaxUserAgentLen-len(customUserAgent)-2+1), "") + if _, ok := err.(*wire.MessageError); !ok { + t.Errorf("AddUserAgent: expected error not received "+ + "- got %v, want %T", err, wire.MessageError{}) + + } + + // Version message should not have any services set by default. + if msg.Services != 0 { + t.Errorf("NewMsgVersion: wrong default services - got %v, want %v", + msg.Services, 0) + + } + if msg.HasService(wire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service is set") + } + + // Ensure the command is expected value. + wantCmd := "version" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgVersion: wrong command - got %v want %v", + cmd, wantCmd) + } + + // Ensure max payload is expected value. + // Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes + + // remote and local net addresses + nonce 8 bytes + length of user agent + // (varInt) + max allowed user agent length + last block 4 bytes + + // relay transactions flag 1 byte. + wantPayload := uint32(2102) + maxPayload := msg.MaxPayloadLength(pver) + if maxPayload != wantPayload { + t.Errorf("MaxPayloadLength: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Ensure adding the full service node flag works. + msg.AddService(wire.SFNodeNetwork) + if msg.Services != wire.SFNodeNetwork { + t.Errorf("AddService: wrong services - got %v, want %v", + msg.Services, wire.SFNodeNetwork) + } + if !msg.HasService(wire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service not set") + } + + // Use a fake connection. + conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou} + msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) + if err != nil { + t.Errorf("NewMsgVersionFromConn: %v", err) + } + + // Ensure we get the correct connection data back out. + if !msg.AddrMe.IP.Equal(tcpAddrMe.IP) { + t.Errorf("NewMsgVersionFromConn: wrong me ip - got %v, want %v", + msg.AddrMe.IP, tcpAddrMe.IP) + } + if !msg.AddrYou.IP.Equal(tcpAddrYou.IP) { + t.Errorf("NewMsgVersionFromConn: wrong you ip - got %v, want %v", + msg.AddrYou.IP, tcpAddrYou.IP) + } + + // Use a fake connection with local UDP addresses to force a failure. + conn = &fakeConn{ + localAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}, + remoteAddr: tcpAddrYou, + } + msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) + if err != wire.ErrInvalidNetAddr { + t.Errorf("NewMsgVersionFromConn: expected error not received "+ + "- got %v, want %v", err, wire.ErrInvalidNetAddr) + } + + // Use a fake connection with remote UDP addresses to force a failure. + conn = &fakeConn{ + localAddr: tcpAddrMe, + remoteAddr: &net.UDPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}, + } + msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) + if err != wire.ErrInvalidNetAddr { + t.Errorf("NewMsgVersionFromConn: expected error not received "+ + "- got %v, want %v", err, wire.ErrInvalidNetAddr) + } + + return +} + +// TestVersionWire tests the MsgVersion wire encode and decode for various +// protocol versions. +func TestVersionWire(t *testing.T) { + // verRelayTxFalse and verRelayTxFalseEncoded is a version message as of + // BIP0037Version with the transaction relay disabled. + baseVersionBIP0037Copy := *baseVersionBIP0037 + verRelayTxFalse := &baseVersionBIP0037Copy + verRelayTxFalse.DisableRelayTx = true + verRelayTxFalseEncoded := make([]byte, len(baseVersionBIP0037Encoded)) + copy(verRelayTxFalseEncoded, baseVersionBIP0037Encoded) + verRelayTxFalseEncoded[len(verRelayTxFalseEncoded)-1] = 0 + + tests := []struct { + in *wire.MsgVersion // Message to encode + out *wire.MsgVersion // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseVersionBIP0037, + baseVersionBIP0037, + baseVersionBIP0037Encoded, + wire.ProtocolVersion, + }, + + // Protocol version BIP0037Version with relay transactions field + // true. + { + baseVersionBIP0037, + baseVersionBIP0037, + baseVersionBIP0037Encoded, + wire.BIP0037Version, + }, + + // Protocol version BIP0037Version with relay transactions field + // false. + { + verRelayTxFalse, + verRelayTxFalse, + verRelayTxFalseEncoded, + wire.BIP0037Version, + }, + + // Protocol version BIP0035Version. + { + baseVersion, + baseVersion, + baseVersionEncoded, + wire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseVersion, + baseVersion, + baseVersionEncoded, + wire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseVersion, + baseVersion, + baseVersionEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseVersion, + baseVersion, + baseVersionEncoded, + wire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode the message to wire format. + var buf bytes.Buffer + err := test.in.BtcEncode(&buf, test.pver) + if err != nil { + t.Errorf("BtcEncode #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("BtcEncode #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var msg wire.MsgVersion + rbuf := bytes.NewBuffer(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestVersionWireErrors performs negative tests against wire encode and +// decode of MsgGetHeaders to confirm error paths work correctly. +func TestVersionWireErrors(t *testing.T) { + // Use protocol version 60002 specifically here instead of the latest + // because the test data is using bytes encoded with that protocol + // version. + pver := uint32(60002) + wireErr := &wire.MessageError{} + + // Ensure calling MsgVersion.BtcDecode with a non *bytes.Buffer returns + // error. + fr := newFixedReader(0, []byte{}) + if err := baseVersion.BtcDecode(fr, pver); err == nil { + t.Errorf("Did not received error when calling " + + "MsgVersion.BtcDecode with non *bytes.Buffer") + } + + // Copy the base version and change the user agent to exceed max limits. + bvc := *baseVersion + exceedUAVer := &bvc + newUA := "/" + strings.Repeat("t", wire.MaxUserAgentLen-8+1) + ":0.0.1/" + exceedUAVer.UserAgent = newUA + + // Encode the new UA length as a varint. + var newUAVarIntBuf bytes.Buffer + err := wire.TstWriteVarInt(&newUAVarIntBuf, pver, uint64(len(newUA))) + if err != nil { + t.Errorf("writeVarInt: error %v", err) + } + + // Make a new buffer big enough to hold the base version plus the new + // bytes for the bigger varint to hold the new size of the user agent + // and the new user agent string. Then stich it all together. + newLen := len(baseVersionEncoded) - len(baseVersion.UserAgent) + newLen = newLen + len(newUAVarIntBuf.Bytes()) - 1 + len(newUA) + exceedUAVerEncoded := make([]byte, newLen) + copy(exceedUAVerEncoded, baseVersionEncoded[0:80]) + copy(exceedUAVerEncoded[80:], newUAVarIntBuf.Bytes()) + copy(exceedUAVerEncoded[83:], []byte(newUA)) + copy(exceedUAVerEncoded[83+len(newUA):], baseVersionEncoded[97:100]) + + tests := []struct { + in *wire.MsgVersion // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Force error in protocol version. + {baseVersion, baseVersionEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error in services. + {baseVersion, baseVersionEncoded, pver, 4, io.ErrShortWrite, io.EOF}, + // Force error in timestamp. + {baseVersion, baseVersionEncoded, pver, 12, io.ErrShortWrite, io.EOF}, + // Force error in remote address. + {baseVersion, baseVersionEncoded, pver, 20, io.ErrShortWrite, io.EOF}, + // Force error in local address. + {baseVersion, baseVersionEncoded, pver, 47, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force error in nonce. + {baseVersion, baseVersionEncoded, pver, 73, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force error in user agent length. + {baseVersion, baseVersionEncoded, pver, 81, io.ErrShortWrite, io.EOF}, + // Force error in user agent. + {baseVersion, baseVersionEncoded, pver, 82, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force error in last block. + {baseVersion, baseVersionEncoded, pver, 98, io.ErrShortWrite, io.ErrUnexpectedEOF}, + // Force error in relay tx - no read error should happen since + // it's optional. + { + baseVersionBIP0037, baseVersionBIP0037Encoded, + wire.BIP0037Version, 101, io.ErrShortWrite, nil, + }, + // Force error due to user agent too big + {exceedUAVer, exceedUAVerEncoded, pver, newLen, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg wire.MsgVersion + buf := bytes.NewBuffer(test.buf[0:test.max]) + err = msg.BtcDecode(buf, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type wire.MessageError, check + // them for equality. + if _, ok := err.(*wire.MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + } +} + +// TestVersionOptionalFields performs tests to ensure that an encoded version +// messages that omit optional fields are handled correctly. +func TestVersionOptionalFields(t *testing.T) { + // onlyRequiredVersion is a version message that only contains the + // required versions and all other values set to their default values. + onlyRequiredVersion := wire.MsgVersion{ + ProtocolVersion: 60002, + Services: wire.SFNodeNetwork, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST) + AddrYou: wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8333, + }, + } + onlyRequiredVersionEncoded := make([]byte, len(baseVersionEncoded)-55) + copy(onlyRequiredVersionEncoded, baseVersionEncoded) + + // addrMeVersion is a version message that contains all fields through + // the AddrMe field. + addrMeVersion := onlyRequiredVersion + addrMeVersion.AddrMe = wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + addrMeVersionEncoded := make([]byte, len(baseVersionEncoded)-29) + copy(addrMeVersionEncoded, baseVersionEncoded) + + // nonceVersion is a version message that contains all fields through + // the Nonce field. + nonceVersion := addrMeVersion + nonceVersion.Nonce = 123123 // 0x1e0f3 + nonceVersionEncoded := make([]byte, len(baseVersionEncoded)-21) + copy(nonceVersionEncoded, baseVersionEncoded) + + // uaVersion is a version message that contains all fields through + // the UserAgent field. + uaVersion := nonceVersion + uaVersion.UserAgent = "/btcdtest:0.0.1/" + uaVersionEncoded := make([]byte, len(baseVersionEncoded)-4) + copy(uaVersionEncoded, baseVersionEncoded) + + // lastBlockVersion is a version message that contains all fields + // through the LastBlock field. + lastBlockVersion := uaVersion + lastBlockVersion.LastBlock = 234234 // 0x392fa + lastBlockVersionEncoded := make([]byte, len(baseVersionEncoded)) + copy(lastBlockVersionEncoded, baseVersionEncoded) + + tests := []struct { + msg *wire.MsgVersion // Expected message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + { + &onlyRequiredVersion, + onlyRequiredVersionEncoded, + wire.ProtocolVersion, + }, + { + &addrMeVersion, + addrMeVersionEncoded, + wire.ProtocolVersion, + }, + { + &nonceVersion, + nonceVersionEncoded, + wire.ProtocolVersion, + }, + { + &uaVersion, + uaVersionEncoded, + wire.ProtocolVersion, + }, + { + &lastBlockVersion, + lastBlockVersionEncoded, + wire.ProtocolVersion, + }, + } + + for i, test := range tests { + // Decode the message from wire format. + var msg wire.MsgVersion + rbuf := bytes.NewBuffer(test.buf) + err := msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(&msg, test.msg) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.msg)) + continue + } + } +} + +// baseVersion is used in the various tests as a baseline MsgVersion. +var baseVersion = &wire.MsgVersion{ + ProtocolVersion: 60002, + Services: wire.SFNodeNetwork, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST) + AddrYou: wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8333, + }, + AddrMe: wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + }, + Nonce: 123123, // 0x1e0f3 + UserAgent: "/btcdtest:0.0.1/", + LastBlock: 234234, // 0x392fa +} + +// baseVersionEncoded is the wire encoded bytes for baseVersion using protocol +// version 60002 and is used in the various tests. +var baseVersionEncoded = []byte{ + 0x62, 0xea, 0x00, 0x00, // Protocol version 60002 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // 64-bit Timestamp + // AddrYou -- No timestamp for NetAddress in version message + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + // AddrMe -- No timestamp for NetAddress in version message + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + 0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Nonce + 0x10, // Varint for user agent length + 0x2f, 0x62, 0x74, 0x63, 0x64, 0x74, 0x65, 0x73, + 0x74, 0x3a, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f, // User agent + 0xfa, 0x92, 0x03, 0x00, // Last block +} + +// baseVersionBIP0037 is used in the various tests as a baseline MsgVersion for +// BIP0037. +var baseVersionBIP0037 = &wire.MsgVersion{ + ProtocolVersion: 70001, + Services: wire.SFNodeNetwork, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST) + AddrYou: wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8333, + }, + AddrMe: wire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + }, + Nonce: 123123, // 0x1e0f3 + UserAgent: "/btcdtest:0.0.1/", + LastBlock: 234234, // 0x392fa +} + +// baseVersionBIP0037Encoded is the wire encoded bytes for baseVersionBIP0037 +// using protocol version BIP0037Version and is used in the various tests. +var baseVersionBIP0037Encoded = []byte{ + 0x71, 0x11, 0x01, 0x00, // Protocol version 70001 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // 64-bit Timestamp + // AddrYou -- No timestamp for NetAddress in version message + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + // AddrMe -- No timestamp for NetAddress in version message + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + 0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Nonce + 0x10, // Varint for user agent length + 0x2f, 0x62, 0x74, 0x63, 0x64, 0x74, 0x65, 0x73, + 0x74, 0x3a, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f, // User agent + 0xfa, 0x92, 0x03, 0x00, // Last block + 0x01, // Relay tx +} diff --git a/wire/netaddress.go b/wire/netaddress.go new file mode 100644 index 00000000..ad6abbf2 --- /dev/null +++ b/wire/netaddress.go @@ -0,0 +1,172 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "encoding/binary" + "errors" + "io" + "net" + "time" +) + +// ErrInvalidNetAddr describes an error that indicates the caller didn't specify +// a TCP address as required. +var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr") + +// maxNetAddressPayload returns the max payload size for a bitcoin NetAddress +// based on the protocol version. +func maxNetAddressPayload(pver uint32) uint32 { + // Services 8 bytes + ip 16 bytes + port 2 bytes. + plen := uint32(26) + + // NetAddressTimeVersion added a timestamp field. + if pver >= NetAddressTimeVersion { + // Timestamp 4 bytes. + plen += 4 + } + + return plen +} + +// NetAddress defines information about a peer on the network including the time +// it was last seen, the services it supports, its IP address, and port. +type NetAddress struct { + // Last time the address was seen. This is, unfortunately, encoded as a + // uint32 on the wire and therefore is limited to 2106. This field is + // not present in the bitcoin version message (MsgVersion) nor was it + // added until protocol version >= NetAddressTimeVersion. + Timestamp time.Time + + // Bitfield which identifies the services supported by the address. + Services ServiceFlag + + // IP address of the peer. + IP net.IP + + // Port the peer is using. This is encoded in big endian on the wire + // which differs from most everything else. + Port uint16 +} + +// HasService returns whether the specified service is supported by the address. +func (na *NetAddress) HasService(service ServiceFlag) bool { + if na.Services&service == service { + return true + } + return false +} + +// AddService adds service as a supported service by the peer generating the +// message. +func (na *NetAddress) AddService(service ServiceFlag) { + na.Services |= service +} + +// SetAddress is a convenience function to set the IP address and port in one +// call. +func (na *NetAddress) SetAddress(ip net.IP, port uint16) { + na.IP = ip + na.Port = port +} + +// NewNetAddressIPPort returns a new NetAddress using the provided IP, port, and +// supported services with defaults for the remaining fields. +func NewNetAddressIPPort(ip net.IP, port uint16, services ServiceFlag) *NetAddress { + // Limit the timestamp to one second precision since the protocol + // doesn't support better. + na := NetAddress{ + Timestamp: time.Unix(time.Now().Unix(), 0), + Services: services, + IP: ip, + Port: port, + } + return &na +} + +// NewNetAddress returns a new NetAddress using the provided TCP address and +// supported services with defaults for the remaining fields. +// +// Note that addr must be a net.TCPAddr. An ErrInvalidNetAddr is returned +// if it is not. +func NewNetAddress(addr net.Addr, services ServiceFlag) (*NetAddress, error) { + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return nil, ErrInvalidNetAddr + } + + na := NewNetAddressIPPort(tcpAddr.IP, uint16(tcpAddr.Port), services) + return na, nil +} + +// readNetAddress 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. +func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error { + var timestamp time.Time + var services ServiceFlag + var ip [16]byte + var port uint16 + + // NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will + // stop working somewhere around 2106. Also timestamp wasn't added until + // protocol version >= NetAddressTimeVersion + if ts && pver >= NetAddressTimeVersion { + var stamp uint32 + err := readElement(r, &stamp) + if err != nil { + return err + } + timestamp = time.Unix(int64(stamp), 0) + } + + err := readElements(r, &services, &ip) + if err != nil { + return err + } + // Sigh. Bitcoin protocol mixes little and big endian. + err = binary.Read(r, binary.BigEndian, &port) + if err != nil { + return err + } + + na.Timestamp = timestamp + na.Services = services + na.SetAddress(net.IP(ip[:]), port) + return nil +} + +// writeNetAddress 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. +func writeNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) 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 { + 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 { + return err + } + + // Sigh. Bitcoin protocol mixes little and big endian. + err = binary.Write(w, binary.BigEndian, na.Port) + if err != nil { + return err + } + + return nil +} diff --git a/wire/netaddress_test.go b/wire/netaddress_test.go new file mode 100644 index 00000000..b7e8775e --- /dev/null +++ b/wire/netaddress_test.go @@ -0,0 +1,294 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "io" + "net" + "reflect" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// TestNetAddress tests the NetAddress API. +func TestNetAddress(t *testing.T) { + ip := net.ParseIP("127.0.0.1") + port := 8333 + + // Test NewNetAddress. + tcpAddr := &net.TCPAddr{ + IP: ip, + Port: port, + } + na, err := wire.NewNetAddress(tcpAddr, 0) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + + // Ensure we get the same ip, port, and services back out. + if !na.IP.Equal(ip) { + t.Errorf("NetNetAddress: wrong ip - got %v, want %v", na.IP, ip) + } + if na.Port != uint16(port) { + t.Errorf("NetNetAddress: wrong port - got %v, want %v", na.Port, + port) + } + if na.Services != 0 { + t.Errorf("NetNetAddress: wrong services - got %v, want %v", + na.Services, 0) + } + if na.HasService(wire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service is set") + } + + // Ensure adding the full service node flag works. + na.AddService(wire.SFNodeNetwork) + if na.Services != wire.SFNodeNetwork { + t.Errorf("AddService: wrong services - got %v, want %v", + na.Services, wire.SFNodeNetwork) + } + if !na.HasService(wire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service not set") + } + + // Ensure max payload is expected value for latest protocol version. + pver := wire.ProtocolVersion + wantPayload := uint32(30) + maxPayload := wire.TstMaxNetAddressPayload(wire.ProtocolVersion) + if maxPayload != wantPayload { + t.Errorf("maxNetAddressPayload: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Protocol version before NetAddressTimeVersion when timestamp was + // added. Ensure max payload is expected value for it. + pver = wire.NetAddressTimeVersion - 1 + wantPayload = 26 + maxPayload = wire.TstMaxNetAddressPayload(pver) + if maxPayload != wantPayload { + t.Errorf("maxNetAddressPayload: wrong max payload length for "+ + "protocol version %d - got %v, want %v", pver, + maxPayload, wantPayload) + } + + // Check for expected failure on wrong address type. + udpAddr := &net.UDPAddr{} + _, err = wire.NewNetAddress(udpAddr, 0) + if err != wire.ErrInvalidNetAddr { + t.Errorf("NewNetAddress: expected error not received - "+ + "got %v, want %v", err, wire.ErrInvalidNetAddr) + } +} + +// TestNetAddressWire tests the NetAddress wire encode and decode for various +// protocol versions and timestamp flag combinations. +func TestNetAddressWire(t *testing.T) { + // baseNetAddr is used in the various tests as a baseline NetAddress. + baseNetAddr := wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + + // baseNetAddrNoTS is baseNetAddr with a zero value for the timestamp. + baseNetAddrNoTS := baseNetAddr + baseNetAddrNoTS.Timestamp = time.Time{} + + // baseNetAddrEncoded is the wire encoded bytes of baseNetAddr. + baseNetAddrEncoded := []byte{ + 0x29, 0xab, 0x5f, 0x49, // Timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + } + + // baseNetAddrNoTSEncoded is the wire encoded bytes of baseNetAddrNoTS. + baseNetAddrNoTSEncoded := []byte{ + // No timestamp + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1 + 0x20, 0x8d, // Port 8333 in big-endian + } + + tests := []struct { + in wire.NetAddress // NetAddress to encode + out wire.NetAddress // Expected decoded NetAddress + ts bool // Include timestamp? + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version without ts flag. + { + baseNetAddr, + baseNetAddrNoTS, + false, + baseNetAddrNoTSEncoded, + wire.ProtocolVersion, + }, + + // Latest protocol version with ts flag. + { + baseNetAddr, + baseNetAddr, + true, + baseNetAddrEncoded, + wire.ProtocolVersion, + }, + + // Protocol version NetAddressTimeVersion without ts flag. + { + baseNetAddr, + baseNetAddrNoTS, + false, + baseNetAddrNoTSEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with ts flag. + { + baseNetAddr, + baseNetAddr, + true, + baseNetAddrEncoded, + wire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion-1 without ts flag. + { + baseNetAddr, + baseNetAddrNoTS, + false, + baseNetAddrNoTSEncoded, + wire.NetAddressTimeVersion - 1, + }, + + // Protocol version NetAddressTimeVersion-1 with timestamp. + // Even though the timestamp flag is set, this shouldn't have a + // timestamp since it is a protocol version before it was + // added. + { + baseNetAddr, + baseNetAddrNoTS, + true, + baseNetAddrNoTSEncoded, + wire.NetAddressTimeVersion - 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := wire.TstWriteNetAddress(&buf, test.pver, &test.in, test.ts) + if err != nil { + t.Errorf("writeNetAddress #%d error %v", i, err) + continue + } + if !bytes.Equal(buf.Bytes(), test.buf) { + t.Errorf("writeNetAddress #%d\n got: %s want: %s", i, + spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) + continue + } + + // Decode the message from wire format. + var na wire.NetAddress + rbuf := bytes.NewReader(test.buf) + err = wire.TstReadNetAddress(rbuf, test.pver, &na, test.ts) + if err != nil { + t.Errorf("readNetAddress #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(na, test.out) { + t.Errorf("readNetAddress #%d\n got: %s want: %s", i, + spew.Sdump(na), spew.Sdump(test.out)) + continue + } + } +} + +// TestNetAddressWireErrors performs negative tests against wire encode and +// decode NetAddress to confirm error paths work correctly. +func TestNetAddressWireErrors(t *testing.T) { + pver := wire.ProtocolVersion + pverNAT := wire.NetAddressTimeVersion - 1 + + // baseNetAddr is used in the various tests as a baseline NetAddress. + baseNetAddr := wire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: wire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + + tests := []struct { + in *wire.NetAddress // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + ts bool // Include timestamp flag + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with timestamp and intentional + // read/write errors. + // Force errors on timestamp. + {&baseNetAddr, []byte{}, pver, true, 0, io.ErrShortWrite, io.EOF}, + // Force errors on services. + {&baseNetAddr, []byte{}, pver, true, 4, io.ErrShortWrite, io.EOF}, + // Force errors on ip. + {&baseNetAddr, []byte{}, pver, true, 12, io.ErrShortWrite, io.EOF}, + // Force errors on port. + {&baseNetAddr, []byte{}, pver, true, 28, io.ErrShortWrite, io.EOF}, + + // Latest protocol version with no timestamp and intentional + // read/write errors. + // Force errors on services. + {&baseNetAddr, []byte{}, pver, false, 0, io.ErrShortWrite, io.EOF}, + // Force errors on ip. + {&baseNetAddr, []byte{}, pver, false, 8, io.ErrShortWrite, io.EOF}, + // Force errors on port. + {&baseNetAddr, []byte{}, pver, false, 24, io.ErrShortWrite, io.EOF}, + + // Protocol version before NetAddressTimeVersion with timestamp + // flag set (should not have timestamp due to old protocol + // version) and intentional read/write errors. + // Force errors on services. + {&baseNetAddr, []byte{}, pverNAT, true, 0, io.ErrShortWrite, io.EOF}, + // Force errors on ip. + {&baseNetAddr, []byte{}, pverNAT, true, 8, io.ErrShortWrite, io.EOF}, + // Force errors on port. + {&baseNetAddr, []byte{}, pverNAT, true, 24, io.ErrShortWrite, io.EOF}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := wire.TstWriteNetAddress(w, test.pver, test.in, test.ts) + if err != test.writeErr { + t.Errorf("writeNetAddress #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // Decode from wire format. + var na wire.NetAddress + r := newFixedReader(test.max, test.buf) + err = wire.TstReadNetAddress(r, test.pver, &na, test.ts) + if err != test.readErr { + t.Errorf("readNetAddress #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + } +} diff --git a/wire/protocol.go b/wire/protocol.go new file mode 100644 index 00000000..ad4e67be --- /dev/null +++ b/wire/protocol.go @@ -0,0 +1,118 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + // ProtocolVersion is the latest protocol version this package supports. + ProtocolVersion uint32 = 70002 + + // MultipleAddressVersion is the protocol version which added multiple + // addresses per message (pver >= MultipleAddressVersion). + MultipleAddressVersion uint32 = 209 + + // NetAddressTimeVersion is the protocol version which added the + // timestamp field (pver >= NetAddressTimeVersion). + NetAddressTimeVersion uint32 = 31402 + + // BIP0031Version is the protocol version AFTER which a pong message + // and nonce field in ping were added (pver > BIP0031Version). + BIP0031Version uint32 = 60000 + + // BIP0035Version is the protocol version which added the mempool + // message (pver >= BIP0035Version). + BIP0035Version uint32 = 60002 + + // BIP0037Version is the protocol version which added new connection + // bloom filtering related messages and extended the version message + // with a relay flag (pver >= BIP0037Version). + BIP0037Version uint32 = 70001 + + // RejectVersion is the protocol version which added a new reject + // message. + RejectVersion uint32 = 70002 +) + +// ServiceFlag identifies services supported by a bitcoin peer. +type ServiceFlag uint64 + +const ( + // SFNodeNetwork is a flag used to indicate a peer is a full node. + SFNodeNetwork ServiceFlag = 1 << iota +) + +// Map of service flags back to their constant names for pretty printing. +var sfStrings = map[ServiceFlag]string{ + SFNodeNetwork: "SFNodeNetwork", +} + +// String returns the ServiceFlag in human-readable form. +func (f ServiceFlag) String() string { + // No flags are set. + if f == 0 { + return "0x0" + } + + // Add individual bit flags. + s := "" + for flag, name := range sfStrings { + if f&flag == flag { + s += name + "|" + f -= flag + } + } + + // Add any remaining flags which aren't accounted for as hex. + s = strings.TrimRight(s, "|") + if f != 0 { + s += "|0x" + strconv.FormatUint(uint64(f), 16) + } + s = strings.TrimLeft(s, "|") + return s +} + +// BitcoinNet represents which bitcoin network a message belongs to. +type BitcoinNet uint32 + +// Constants used to indicate the message bitcoin network. They can also be +// used to seek to the next message when a stream's state is unknown, but +// this package does not provide that functionality since it's generally a +// better idea to simply disconnect clients that are misbehaving over TCP. +const ( + // MainNet represents the main bitcoin network. + MainNet BitcoinNet = 0xd9b4bef9 + + // TestNet represents the regression test network. + TestNet BitcoinNet = 0xdab5bffa + + // TestNet3 represents the test network (version 3). + TestNet3 BitcoinNet = 0x0709110b + + // SimNet represents the simulation test network. + SimNet BitcoinNet = 0x12141c16 +) + +// bnStrings is a map of bitcoin networks back to their constant names for +// pretty printing. +var bnStrings = map[BitcoinNet]string{ + MainNet: "MainNet", + TestNet: "TestNet", + TestNet3: "TestNet3", + SimNet: "SimNet", +} + +// String returns the BitcoinNet in human-readable form. +func (n BitcoinNet) String() string { + if s, ok := bnStrings[n]; ok { + return s + } + + return fmt.Sprintf("Unknown BitcoinNet (%d)", uint32(n)) +} diff --git a/wire/protocol_test.go b/wire/protocol_test.go new file mode 100644 index 00000000..8d067238 --- /dev/null +++ b/wire/protocol_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "testing" + + "github.com/btcsuite/btcd/wire" +) + +// TestServiceFlagStringer tests the stringized output for service flag types. +func TestServiceFlagStringer(t *testing.T) { + tests := []struct { + in wire.ServiceFlag + want string + }{ + {0, "0x0"}, + {wire.SFNodeNetwork, "SFNodeNetwork"}, + {0xffffffff, "SFNodeNetwork|0xfffffffe"}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result := test.in.String() + if result != test.want { + t.Errorf("String #%d\n got: %s want: %s", i, result, + test.want) + continue + } + } +} + +// TestBitcoinNetStringer tests the stringized output for bitcoin net types. +func TestBitcoinNetStringer(t *testing.T) { + tests := []struct { + in wire.BitcoinNet + want string + }{ + {wire.MainNet, "MainNet"}, + {wire.TestNet, "TestNet"}, + {wire.TestNet3, "TestNet3"}, + {wire.SimNet, "SimNet"}, + {0xffffffff, "Unknown BitcoinNet (4294967295)"}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result := test.in.String() + if result != test.want { + t.Errorf("String #%d\n got: %s want: %s", i, result, + test.want) + continue + } + } +} diff --git a/wire/shahash.go b/wire/shahash.go new file mode 100644 index 00000000..63f30ac0 --- /dev/null +++ b/wire/shahash.go @@ -0,0 +1,108 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "encoding/hex" + "fmt" +) + +// Size of array used to store sha hashes. See ShaHash. +const HashSize = 32 + +// MaxHashStringSize is the maximum length of a ShaHash hash string. +const MaxHashStringSize = HashSize * 2 + +// ErrHashStrSize describes an error that indicates the caller specified a hash +// string that has too many characters. +var ErrHashStrSize = fmt.Errorf("max hash string length is %v bytes", MaxHashStringSize) + +// ShaHash is used in several of the bitcoin messages and common structures. It +// typically represents the double sha256 of data. +type ShaHash [HashSize]byte + +// String returns the ShaHash as the hexadecimal string of the byte-reversed +// hash. +func (hash ShaHash) String() string { + for i := 0; i < HashSize/2; i++ { + hash[i], hash[HashSize-1-i] = hash[HashSize-1-i], hash[i] + } + return hex.EncodeToString(hash[:]) +} + +// Bytes returns the bytes which represent the hash as a byte slice. +func (hash *ShaHash) Bytes() []byte { + newHash := make([]byte, HashSize) + copy(newHash, hash[:]) + + return newHash +} + +// SetBytes sets the bytes which represent the hash. An error is returned if +// the number of bytes passed in is not HashSize. +func (hash *ShaHash) SetBytes(newHash []byte) error { + nhlen := len(newHash) + if nhlen != HashSize { + return fmt.Errorf("invalid sha length of %v, want %v", nhlen, + HashSize) + } + copy(hash[:], newHash[0:HashSize]) + + return nil +} + +// IsEqual returns true if target is the same as hash. +func (hash *ShaHash) IsEqual(target *ShaHash) bool { + return bytes.Equal(hash[:], target[:]) +} + +// NewShaHash returns a new ShaHash from a byte slice. An error is returned if +// the number of bytes passed in is not HashSize. +func NewShaHash(newHash []byte) (*ShaHash, error) { + var sh ShaHash + err := sh.SetBytes(newHash) + if err != nil { + return nil, err + } + return &sh, err +} + +// NewShaHashFromStr creates a ShaHash from a hash string. The string should be +// the hexadecimal string of a byte-reversed hash, but any missing characters +// result in zero padding at the end of the ShaHash. +func NewShaHashFromStr(hash string) (*ShaHash, error) { + // Return error if hash string is too long. + if len(hash) > MaxHashStringSize { + return nil, ErrHashStrSize + } + + // Hex decoder expects the hash to be a multiple of two. + if len(hash)%2 != 0 { + hash = "0" + hash + } + + // Convert string hash to bytes. + buf, err := hex.DecodeString(hash) + if err != nil { + return nil, err + } + + // Un-reverse the decoded bytes, copying into in leading bytes of a + // ShaHash. There is no need to explicitly pad the result as any + // missing (when len(buf) < HashSize) bytes from the decoded hex string + // will remain zeros at the end of the ShaHash. + var ret ShaHash + blen := len(buf) + mid := blen / 2 + if blen%2 != 0 { + mid++ + } + blen-- + for i, b := range buf[:mid] { + ret[i], ret[blen-i] = buf[blen-i], b + } + return &ret, nil +} diff --git a/wire/shahash_test.go b/wire/shahash_test.go new file mode 100644 index 00000000..2e646b74 --- /dev/null +++ b/wire/shahash_test.go @@ -0,0 +1,182 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/wire" +) + +// TestShaHash tests the ShaHash API. +func TestShaHash(t *testing.T) { + + // Hash of block 234439. + blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" + blockHash, err := wire.NewShaHashFromStr(blockHashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Hash of block 234440 as byte slice. + buf := []byte{ + 0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1, + 0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8, + 0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f, + 0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + hash, err := wire.NewShaHash(buf) + if err != nil { + t.Errorf("NewShaHash: unexpected error %v", err) + } + + // Ensure proper size. + if len(hash) != wire.HashSize { + t.Errorf("NewShaHash: hash length mismatch - got: %v, want: %v", + len(hash), wire.HashSize) + } + + // Ensure contents match. + if !bytes.Equal(hash[:], buf) { + t.Errorf("NewShaHash: hash contents mismatch - got: %v, want: %v", + hash[:], buf) + } + + // Ensure contents of hash of block 234440 don't match 234439. + if hash.IsEqual(blockHash) { + t.Errorf("IsEqual: hash contents should not match - got: %v, want: %v", + hash, blockHash) + } + + // Set hash from byte slice and ensure contents match. + err = hash.SetBytes(blockHash.Bytes()) + if err != nil { + t.Errorf("SetBytes: %v", err) + } + if !hash.IsEqual(blockHash) { + t.Errorf("IsEqual: hash contents mismatch - got: %v, want: %v", + hash, blockHash) + } + + // Invalid size for SetBytes. + err = hash.SetBytes([]byte{0x00}) + if err == nil { + t.Errorf("SetBytes: failed to received expected err - got: nil") + } + + // Invalid size for NewShaHash. + invalidHash := make([]byte, wire.HashSize+1) + _, err = wire.NewShaHash(invalidHash) + if err == nil { + t.Errorf("NewShaHash: failed to received expected err - got: nil") + } +} + +// TestShaHashString tests the stringized output for sha hashes. +func TestShaHashString(t *testing.T) { + // Block 100000 hash. + wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hash := wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39, + 0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2, + 0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa, + 0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + }) + + hashStr := hash.String() + if hashStr != wantStr { + t.Errorf("String: wrong hash string - got %v, want %v", + hashStr, wantStr) + } +} + +// TestNewShaHashFromStr executes tests against the NewShaHashFromStr function. +func TestNewShaHashFromStr(t *testing.T) { + tests := []struct { + in string + want wire.ShaHash + err error + }{ + // Genesis hash. + { + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + mainNetGenesisHash, + nil, + }, + + // Genesis hash with stripped leading zeros. + { + "19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + mainNetGenesisHash, + nil, + }, + + // Empty string. + { + "", + wire.ShaHash{}, + nil, + }, + + // Single digit hash. + { + "1", + wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 0x01, 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, + }), + nil, + }, + + // Block 203707 with stripped leading zeros. + { + "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc", + wire.ShaHash([wire.HashSize]byte{ // Make go vet happy. + 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, + 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, + 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, + 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }), + nil, + }, + + // Hash string that is too long. + { + "01234567890123456789012345678901234567890123456789012345678912345", + wire.ShaHash{}, + wire.ErrHashStrSize, + }, + + // Hash string that is contains non-hex chars. + { + "abcdefg", + wire.ShaHash{}, + hex.InvalidByteError('g'), + }, + } + + unexpectedErrStr := "NewShaHashFromStr #%d failed to detect expected error - got: %v want: %v" + unexpectedResultStr := "NewShaHashFromStr #%d got: %v want: %v" + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + result, err := wire.NewShaHashFromStr(test.in) + if err != test.err { + t.Errorf(unexpectedErrStr, i, err, test.err) + continue + } else if err != nil { + // Got expected error. Move on to the next test. + continue + } + if !test.want.IsEqual(result) { + t.Errorf(unexpectedResultStr, i, result, &test.want) + continue + } + } +}