diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0d760cbb --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Conformal Systems LLC. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 90fb26df..cdacac5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,124 @@ btcwire ======= -Package btcwire implements the bitcoin wire protocol. \ No newline at end of file +Package btcwire implements the bitcoin wire protocol. A comprehensive suite of +tests is provided to ensure proper functionality. See `test_coverage.txt` for +the gocov coverage report. Alternatively, if you are running a POSIX OS, you +can run the `cov_report.sh` script for a real-time report. Package btcwire is +licensed under the liberal ISC license. + +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/). + +## Documentation + +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/conformal/btcwire + +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/conformal/btcwire + +## Installation + +```bash +$ go get github.com/conformal/btcwire +``` + +## 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 verison supported by the package and the + // main bitcoin network. + pver := btcwire.ProtocolVersion + btcnet := btcwire.MainNet + + // Reads and validates the next bitcoin message from conn using the + // protocol version pver and the bitcoin network btcnet. The returns + // are a btcwire.Message, a []byte which contains the unmarshalled + // raw payload, and a possible error. + msg, rawPayload, err := btcwire.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 verison supported by the package and the + // main bitcoin network. + pver := btcwire.ProtocolVersion + btcnet := btcwire.MainNet + + // Create a new getaddr bitcoin message. + msg := btcwire.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 := btcwire.WriteMessage(conn, msg, pver, btcnet) + if err != nil { + // Log and handle the error + } +``` + +## TODO + +- Implement functions for [BIP 0014](https://en.bitcoin.it/wiki/BIP_0014) +- Implement alert message decoding/encoding +- Implement bloom filter messages (filterload, filteradd, filterclear, + merkleblock) as defined in [BIP 0037](https://en.bitcoin.it/wiki/BIP_0037) +- Increase test coverage to 100% + +## 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 btcwire is licensed under the liberal ISC License. diff --git a/blockheader.go b/blockheader.go new file mode 100644 index 00000000..e2e93228 --- /dev/null +++ b/blockheader.go @@ -0,0 +1,119 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "bytes" + "io" + "time" +) + +// BlockVersion is the current latest supported block version. +const BlockVersion uint32 = 2 + +// Version 4 bytes + Timestamp 4 bytes + Bits 4 bytes + Nonce 4 bytes + +// TxnCount (varInt) + PrevBlock and MarkleRoot hashes. +const maxBlockHeaderPayload = 16 + maxVarIntPayload + (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 uint32 + + // 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 + + // Number of transactions in the block. For the bitcoin headers + // (MsgHeaders) message, this must be 0. This is encoded as a variable + // length integer on the wire. + TxnCount uint64 +} + +// blockHashLen is a constant that represents how much of the block header is +// used when computing the block sha 0:blockHashLen +const blockHashLen = 80 + +// BlockSha computes the block identifier hash for the given block header. +func (h *BlockHeader) BlockSha(pver uint32) (sha ShaHash, err error) { + var buf bytes.Buffer + err = writeBlockHeader(&buf, pver, h) + if err != nil { + return + } + + err = sha.SetBytes(DoubleSha256(buf.Bytes()[0:blockHashLen])) + if err != nil { + return + } + + return +} + +// 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 { + + return &BlockHeader{ + Version: BlockVersion, + PrevBlock: *prevHash, + MerkleRoot: *merkleRootHash, + Timestamp: time.Now(), + Bits: bits, + Nonce: nonce, + TxnCount: 0, + } +} + +// readBlockHeader reads a bitcoin block header from r. +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) + + count, err := readVarInt(r, pver) + if err != nil { + return err + } + bh.TxnCount = count + + return nil +} + +// writeBlockHeader writes a bitcoin block header to w. +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 + } + + err = writeVarInt(w, pver, bh.TxnCount) + if err != nil { + return err + } + + return nil +} diff --git a/blockheader_test.go b/blockheader_test.go new file mode 100644 index 00000000..ca352883 --- /dev/null +++ b/blockheader_test.go @@ -0,0 +1,160 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" + "time" +) + +// TestBlockHeader tests the BlockHeader API. +func TestBlockHeader(t *testing.T) { + nonce64, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + nonce := uint32(nonce64) + + hash := btcwire.GenesisHash + merkleHash := btcwire.GenesisMerkleRoot + bits := uint32(0x1d00ffff) + bh := btcwire.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. + hash := btcwire.GenesisHash + merkleHash := btcwire.GenesisMerkleRoot + bits := uint32(0x1d00ffff) + baseBlockHdr := &btcwire.BlockHeader{ + Version: 1, + PrevBlock: hash, + MerkleRoot: merkleHash, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Bits: bits, + Nonce: nonce, + TxnCount: 0, + } + + // 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 + 0x00, // TxnCount Varint + } + + tests := []struct { + in *btcwire.BlockHeader // Data to encode + out *btcwire.BlockHeader // Expected decoded data + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseBlockHdr, + baseBlockHdr, + baseBlockHdrEncoded, + btcwire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := btcwire.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 btcwire.BlockHeader + rbuf := bytes.NewBuffer(test.buf) + err = btcwire.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 + } + } +} diff --git a/common.go b/common.go new file mode 100644 index 00000000..28c8f325 --- /dev/null +++ b/common.go @@ -0,0 +1,181 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "io" + "math" +) + +// 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 { + 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 { + 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) { + b := make([]byte, 1) + _, err := r.Read(b) + if err != nil { + return 0, err + } + + var rv uint64 + discriminant := uint8(b[0]) + switch discriminant { + case 0xff: + var u uint64 + err = binary.Read(r, binary.LittleEndian, &u) + if err != nil { + return 0, err + } + rv = u + + case 0xfe: + var u uint32 + err = binary.Read(r, binary.LittleEndian, &u) + if err != nil { + return 0, err + } + rv = uint64(u) + + case 0xfd: + var u uint16 + err = binary.Read(r, binary.LittleEndian, &u) + if err != nil { + return 0, err + } + rv = uint64(u) + + 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 > math.MaxUint32 { + err := writeElements(w, []byte{0xff}, uint64(val)) + if err != nil { + return err + } + return nil + } + if val > math.MaxUint16 { + err := writeElements(w, []byte{0xfe}, uint32(val)) + if err != nil { + return err + } + return nil + } + if val >= 0xfd { + err := writeElements(w, []byte{0xfd}, uint16(val)) + if err != nil { + return err + } + return nil + } + return writeElement(w, uint8(val)) +} + +// 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. +func readVarString(r io.Reader, pver uint32) (string, error) { + slen, err := readVarInt(r, pver) + if err != nil { + return "", err + } + buf := make([]byte, slen) + err = readElement(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 = writeElement(w, []byte(str)) + 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 by passing a fake reader in the tests. +func randomUint64(r io.Reader) (uint64, error) { + b := make([]byte, 8) + n, err := r.Read(b) + if n != len(b) { + return 0, io.ErrShortBuffer + } + 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 := sha256.New() + hasher.Write(b) + sum := hasher.Sum(nil) + hasher.Reset() + hasher.Write(sum) + sum = hasher.Sum(nil) + return sum +} diff --git a/common_test.go b/common_test.go new file mode 100644 index 00000000..ab6e64de --- /dev/null +++ b/common_test.go @@ -0,0 +1,304 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "fmt" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "io" + "strings" + "testing" +) + +// 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 p. +func (r *fakeRandReader) Read(p []byte) (int, error) { + n := r.n + if n > len(p) { + n = len(p) + } + return n, r.err +} + +// TestVarIntWire tests wire encode and decode for variable length integers. +func TestVarIntWire(t *testing.T) { + pver := btcwire.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 := btcwire.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.NewBuffer(test.buf) + val, err := btcwire.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 := btcwire.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 + }{ + // Latest protocol version with intentional read/write errors. + // 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 := btcwire.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 = btcwire.TstReadVarInt(r, test.pver) + if err != test.readErr { + t.Errorf("readVarInt #%d wrong error got: %v, want :%v", + i, err, test.readErr) + continue + } + } +} + +// TestVarStringWire tests wire encode and decode for variable length strings. +func TestVarStringWire(t *testing.T) { + pver := btcwire.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 := btcwire.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.NewBuffer(test.buf) + val, err := btcwire.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: %d want: %d", 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 := btcwire.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 := btcwire.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 = btcwire.TstReadVarString(r, test.pver) + if err != test.readErr { + t.Errorf("readVarString #%d wrong error got: %v, want :%v", + i, err, test.readErr) + 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 := btcwire.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: nil} + nonce, err := btcwire.TstRandomUint64(fr) + if err != io.ErrShortBuffer { + 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) + } + + // Test err with full read. + fr = &fakeRandReader{n: 20, err: io.ErrClosedPipe} + nonce, err = btcwire.TstRandomUint64(fr) + if err != io.ErrClosedPipe { + t.Errorf("TestRandomUint64Fails: Error not expected value of %v [%v]", + io.ErrClosedPipe, err) + } + if nonce != 0 { + t.Errorf("TestRandomUint64Fails: nonce is not 0 [%v]", nonce) + } +} diff --git a/cov_report.sh b/cov_report.sh new file mode 100644 index 00000000..ef5cf816 --- /dev/null +++ b/cov_report.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script uses gocov to generate a test coverage report. +# The gocov tool my be obtained with the following command: +# go get github.com/awx/gocov/gocov +# +# It will be installed it $GOPATH/bin, so ensure that location is in your $PATH. + +# Check for gocov. +type gocov >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo >&2 "This script requires the gocov tool." + echo >&2 "You may obtain it with the following command:" + echo >&2 "go get github.com/awx/gocov/gocov" + exit 1 +fi +gocov test | gocov report diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..b7086aed --- /dev/null +++ b/doc.go @@ -0,0 +1,163 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package btcwire 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 btcwire.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 +and which bitcoin network the message applies to. This package provides the +following constants: + + btcwire.MainNet + btcwire.TestNet + btcwire.TestNet3 + +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.(type) { + case *btcwire.MsgVersion: + // The message is a pointer to a MsgVersion struct. + fmt.Printf("Protocol version: %v", msg.ProtocolVersion) + case *btcwire.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 btcwire.Message, a []byte which contains the unmarshalled + // raw payload, and a possible error. + msg, rawPayload, err := btcwire.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 := btcwire.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 := btcwire.WriteMessage(conn, msg, pver, btcnet) + if err != nil { + // Log and handle the error + } + +Errors + +Most errors returned by this package are either the raw errors provided by +underlying calls to read/write from streams, or raw strings that describe +the error. See the documentation of each function for any exceptions. NOTE: +This will change soon as the package should return errors that can be +programatically tested. + +Bitcoin Improvement Proposals + +This package includes spec changes outlined by the following BIPs: + + BIP0031 (https://en.bitcoin.it/wiki/BIP_0031) + BIP0035 (https://en.bitcoin.it/wiki/BIP_0035) + +Other important information + +The package does not yet implement BIP0037 (https://en.bitcoin.it/wiki/BIP_0037) +and therefore does not recognize filterload, filteradd, filterclear, or +merkleblock messages. +*/ +package btcwire diff --git a/fakeconn_test.go b/fakeconn_test.go new file mode 100644 index 00000000..3c6f6bc6 --- /dev/null +++ b/fakeconn_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_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/fakemessage_test.go b/fakemessage_test.go new file mode 100644 index 00000000..3288d93e --- /dev/null +++ b/fakemessage_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "io" +) + +// fakeMessage implements the btcwire.Message interface and is used to force +// errors. +type fakeMessage struct { + command string + maxPayload uint32 +} + +// BtcDecode doesn't do anything. It just satisfies the btcwire.Message +// interface. +func (msg *fakeMessage) BtcDecode(r io.Reader, pver uint32) error { + return nil +} + +// BtcEncode doesn't do anything. It just satisfies the btcwire.Message +// interface. +func (msg *fakeMessage) BtcEncode(w io.Writer, pver uint32) error { + return nil +} + +// Command returns the command field of the fake message and satisfies the +// btcwire.Message interface. +func (msg *fakeMessage) Command() string { + return msg.command +} + +// Command returns the maxPayload field of the fake message and satisfies the +// btcwire.Message interface. +func (msg *fakeMessage) MaxPayloadLength(pver uint32) uint32 { + return msg.maxPayload +} diff --git a/fixedIO_test.go b/fixedIO_test.go new file mode 100644 index 00000000..7ea0377d --- /dev/null +++ b/fixedIO_test.go @@ -0,0 +1,67 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_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 ... +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 ... +func (w *fixedWriter) Bytes() []byte { + return w.b +} + +// newFixedWriter... +func newFixedWriter(max int) *fixedWriter { + 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 .... +func (fr *fixedReader) Read(p []byte) (n int, err error) { + n, err = fr.iobuf.Read(p) + fr.pos += n + return +} + +// newFixedReader ... +func newFixedReader(max int, buf []byte) *fixedReader { + 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/genesis.go b/genesis.go new file mode 100644 index 00000000..0ab72fa3 --- /dev/null +++ b/genesis.go @@ -0,0 +1,83 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "time" +) + +// GenesisHash is the hash of the first block in the block chain (genesis +// block). +var GenesisHash ShaHash = ShaHash{ + 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, +} + +// GenesisMerkleRoot is the hash of the first transaction in the genesis block. +var GenesisMerkleRoot ShaHash = ShaHash{ + 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, +} + +// GenesisBlock defines the genesis block of the block chain which serves as the +// public transaction ledger. +var GenesisBlock MsgBlock = MsgBlock{ + Header: BlockHeader{ + Version: 1, + PrevBlock: ShaHash{}, // 0000000000000000000000000000000000000000000000000000000000000000 + MerkleRoot: GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Bits: 0x1d00ffff, // 486604799 + Nonce: 0x7c2bac1d, // 2083236893 + TxnCount: 1, + }, + Transactions: []*MsgTx{ + &MsgTx{ + Version: 1, + TxIn: []*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{ + &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, + }, + }, +} diff --git a/genesis_test.go b/genesis_test.go new file mode 100644 index 00000000..75bd6bdf --- /dev/null +++ b/genesis_test.go @@ -0,0 +1,87 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "testing" +) + +// TestGenesisBlock tests the genesis block for validity by checking the encoded +// bytes and hashes. +func TestGenesisBlock(t *testing.T) { + pver := uint32(60002) + + // Encode the genesis block to raw bytes. + var buf bytes.Buffer + err := btcwire.GenesisBlock.BtcEncode(&buf, pver) + if err != nil { + t.Errorf("TestGenesisBlock: %v", err) + return + } + + // Ensure the encoded block matches the expected bytes. + if !bytes.Equal(buf.Bytes(), genesisBlockBytes) { + t.Errorf("TestGenesisBlock: Genesis block does not appear valid - "+ + "got %v, want %v", spew.Sdump(buf.Bytes()), + spew.Sdump(genesisBlockBytes)) + return + } + + // Check hash of the block against expected hash. + hash, err := btcwire.GenesisBlock.Header.BlockSha(pver) + if err != nil { + t.Errorf("BlockSha: %v", err) + } + if !btcwire.GenesisHash.IsEqual(&hash) { + t.Errorf("TestGenesisBlock: Genesis block hash does not appear valid - "+ + "got %v, want %v", spew.Sdump(hash), + spew.Sdump(btcwire.GenesisHash)) + return + } +} + +// genesisBlockBytes are the wire encoded bytes for the genesis block as of +// protocol version 60002. +var genesisBlockBytes = []byte{ + 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, /* |........| */ + 0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */ + 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */ + 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */ + 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */ + 0x4b, 0x1e, 0x5e, 0x4a, 0x29, 0xab, 0x5f, 0x49, /* |K.^J)._I| */ + 0xff, 0xff, 0x00, 0x1d, 0x1d, 0xac, 0x2b, 0x7c, /* |......+|| */ + 0x01, 0x01, 0x00, 0x00, 0x00, 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, 0x00, 0xff, 0xff, /* |........| */ + 0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */ + 0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */ + 0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */ + 0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */ + 0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */ + 0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */ + 0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */ + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */ + 0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */ + 0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */ + 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */ + 0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */ + 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */ + 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */ + 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */ + 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */ + 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */ + 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */ + 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */ + 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/ + 0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */ +} diff --git a/internal_test.go b/internal_test.go new file mode 100644 index 00000000..d57754a5 --- /dev/null +++ b/internal_test.go @@ -0,0 +1,100 @@ +// Copyright (c) 2013 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 btcwire package rather than than the +btcwire_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 btcwire + +import ( + "io" +) + +// TstRandomUint64 makes the internal randomUint64 function available to the +// test package. +func TstRandomUint64(r io.Reader) (uint64, error) { + return randomUint64(r) +} + +// 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) +} + +// 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) +} + +// TstReadMessage makes the internal readMessage function available to +// the test package. +func TstReadMessage(r io.Reader, pver uint32, hdr *messageHeader) (Message, []byte, error) { + return readMessage(r, pver, hdr) +} + +// TstReadMessageHeader makes the internal readMessageHeader function available +// to the test package. +func TstReadMessageHeader(r io.Reader) (*messageHeader, error) { + return readMessageHeader(r) +} diff --git a/invvect.go b/invvect.go new file mode 100644 index 00000000..1784a094 --- /dev/null +++ b/invvect.go @@ -0,0 +1,79 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 + +const ( + InvVect_Error InvType = 0 + InvVect_Tx InvType = 1 + InvVect_Block InvType = 2 +) + +// Map of service flags back to their constant names for pretty printing. +var ivStrings = map[InvType]string{ + InvVect_Error: "ERROR", + InvVect_Tx: "MSG_TX", + InvVect_Block: "MSG_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/invvect_test.go b/invvect_test.go new file mode 100644 index 00000000..d374c976 --- /dev/null +++ b/invvect_test.go @@ -0,0 +1,268 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestInvVectStringer tests the stringized output for inventory vector types. +func TestInvTypeStringer(t *testing.T) { + tests := []struct { + in btcwire.InvType + want string + }{ + {btcwire.InvVect_Error, "ERROR"}, + {btcwire.InvVect_Tx, "MSG_TX"}, + {btcwire.InvVect_Block, "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 := btcwire.InvVect_Block + hash := btcwire.ShaHash{} + + // Ensure we get the same payload and signature back out. + iv := btcwire.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 := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // errInvVect is an inventory vector with an error. + errInvVect := btcwire.InvVect{ + Type: btcwire.InvVect_Error, + Hash: btcwire.ShaHash{}, + } + + // errInvVectEncoded is the wire encoded bytes of errInvVect. + errInvVectEncoded := []byte{ + 0x00, 0x00, 0x00, 0x00, // InvVect_Error + 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 := btcwire.InvVect{ + Type: btcwire.InvVect_Tx, + Hash: *baseHash, + } + + // txInvVectEncoded is the wire encoded bytes of txInvVect. + txInvVectEncoded := []byte{ + 0x01, 0x00, 0x00, 0x00, // InvVect_Tx + 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 := btcwire.InvVect{ + Type: btcwire.InvVect_Block, + Hash: *baseHash, + } + + // blockInvVectEncoded is the wire encoded bytes of blockInvVect. + blockInvVectEncoded := []byte{ + 0x02, 0x00, 0x00, 0x00, // InvVect_Block + 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 btcwire.InvVect // NetAddress to encode + out btcwire.InvVect // Expected decoded NetAddress + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + btcwire.ProtocolVersion, + }, + + // Latest protocol version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + btcwire.ProtocolVersion, + }, + + // Latest protocol version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion error inventory vector. + { + errInvVect, + errInvVect, + errInvVectEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion tx inventory vector. + { + txInvVect, + txInvVect, + txInvVectEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion block inventory vector. + { + blockInvVect, + blockInvVect, + blockInvVectEncoded, + btcwire.MultipleAddressVersion, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := btcwire.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 btcwire.InvVect + rbuf := bytes.NewBuffer(test.buf) + err = btcwire.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/message.go b/message.go new file mode 100644 index 00000000..dea9668b --- /dev/null +++ b/message.go @@ -0,0 +1,286 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "bytes" + "fmt" + "io" + "unicode/utf8" +) + +// 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 byes 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" +) + +// 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{} + + 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 messager header from r. +func readMessageHeader(r io.Reader) (*messageHeader, error) { + var command [commandSize]byte + + hdr := messageHeader{} + err := readElements(r, &hdr.magic, &command, &hdr.length, &hdr.checksum) + if err != nil { + return nil, err + } + + // Strip trailing zeros from command string. + hdr.command = string(bytes.TrimRight(command[:], string(0))) + + // Enforce maximum message payload. + if hdr.length > maxMessagePayload { + str := "readMessageHeader: message payload is too large - " + + "Header indicates %d bytes, but max message payload is %d bytes." + return nil, fmt.Errorf(str, hdr.length, maxMessagePayload) + } + + return &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(10240) // 2k 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) + } +} + +// readMessage reads the next bitcoin message from r for the provided protocol +// version and message header. +func readMessage(r io.Reader, pver uint32, hdr *messageHeader) (Message, []byte, error) { + if hdr == nil { + return nil, nil, fmt.Errorf("readMessage: nil header") + } + + command := hdr.command + if !utf8.ValidString(command) { + discardInput(r, hdr.length) + str := "readMessage: invalid command %v" + return nil, nil, fmt.Errorf(str, []byte(command)) + } + + // Create struct of appropriate message type based on the command. + msg, err := makeEmptyMessage(command) + if err != nil { + discardInput(r, hdr.length) + return nil, nil, fmt.Errorf("readMessage: %v", err) + } + + // 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 := "ReadMessage: payload exceeds max length - Header " + + "indicates %v bytes, but max payload size for messages of type " + + "[%v] is %v." + return nil, nil, fmt.Errorf(str, hdr.length, command, mpl) + } + + // Read payload. + payload := make([]byte, hdr.length) + n, err := io.ReadFull(r, payload) + if err != nil { + return nil, nil, err + } + if uint32(n) != hdr.length { + str := "readMessage: failed to read payload - Read %v " + + "bytes, but payload size is %v bytes." + return nil, nil, fmt.Errorf(str, n, hdr.length) + } + + // Test checksum. + checksum := DoubleSha256(payload)[0:4] + if !bytes.Equal(checksum[:], hdr.checksum[:]) { + str := "readMessage: payload checksum failed - Header " + + "indicates %v, but actual checksum is %v." + return nil, nil, fmt.Errorf(str, hdr.checksum, checksum) + } + + // Unmarshal message. + pr := bytes.NewBuffer(payload) + err = msg.BtcDecode(pr, pver) + if err != nil { + return nil, nil, err + } + + return msg, payload, nil +} + +// WriteMessage writes a bitcoin Message to w including the necessary header +// information. +func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error { + var command [commandSize]byte + + cmd := msg.Command() + if len(cmd) > commandSize { + str := "WriteMessage: command is too long [%s]" + return fmt.Errorf(str, command) + } + copy(command[:], []byte(cmd)) + + var bw bytes.Buffer + err := msg.BtcEncode(&bw, pver) + if err != nil { + return err + } + payload := bw.Bytes() + lenp := len(payload) + + // Enforce maximum message payload. + if lenp > maxMessagePayload { + str := "WriteMessage: message payload is too large - " + + "Encoded %d bytes, but maximum message payload is %d bytes." + return fmt.Errorf(str, lenp, maxMessagePayload) + } + + // Create header for the message. + hdr := messageHeader{} + hdr.magic = btcnet + hdr.command = cmd + hdr.length = uint32(lenp) + copy(hdr.checksum[:], DoubleSha256(payload)[0:4]) + + // Write header. + err = writeElements(w, hdr.magic, command, hdr.length, hdr.checksum) + if err != nil { + return err + } + + // Write payload. + n, err := w.Write(payload) + if err != nil { + return err + } + if n != lenp { + str := "WriteMessage: failed to write payload. " + + "Wrote %v bytes, but payload size is %v bytes." + return fmt.Errorf(str, n, lenp) + } + return nil +} + +// ReadMessage reads, validates, and parses the next bitcoin Message from r. +func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) { + hdr, err := readMessageHeader(r) + if err != nil { + return nil, nil, err + } + + if hdr.magic != btcnet { + str := "ReadMessage: message from other network [%v]" + return nil, nil, fmt.Errorf(str, hdr.magic) + } + + return readMessage(r, pver, hdr) +} diff --git a/message_test.go b/message_test.go new file mode 100644 index 00000000..27756e9e --- /dev/null +++ b/message_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "net" + "reflect" + "testing" + "time" +) + +// TestMessage tests the Read/WriteMessage API. +func TestMessage(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Create the various types of messages to test. + + // MsgVersion. + addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} + you, err := btcwire.NewNetAddress(addrYou, btcwire.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 := btcwire.NewNetAddress(addrMe, btcwire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + me.Timestamp = time.Time{} // Version message has zero value timestamp. + msgVersion := btcwire.NewMsgVersion(me, you, 123123, "/test:0.0.1/", 0) + + msgVerack := btcwire.NewMsgVerAck() + msgGetAddr := btcwire.NewMsgGetAddr() + msgAddr := btcwire.NewMsgAddr() + msgGetBlocks := btcwire.NewMsgGetBlocks(&btcwire.ShaHash{}) + msgBlock := &blockOne + msgInv := btcwire.NewMsgInv() + msgGetData := btcwire.NewMsgGetData() + msgNotFound := btcwire.NewMsgNotFound() + msgTx := btcwire.NewMsgTx() + msgPing := btcwire.NewMsgPing(123123) + msgPong := btcwire.NewMsgPong(123123) + msgGetHeaders := btcwire.NewMsgGetHeaders() + msgHeaders := btcwire.NewMsgHeaders() + + tests := []struct { + in btcwire.Message // Value to encode + out btcwire.Message // Expected decoded value + pver uint32 // Protocol version for wire encoding + btcnet btcwire.BitcoinNet // Network to use for wire encoding + }{ + {msgVersion, msgVersion, pver, btcwire.MainNet}, + {msgVerack, msgVerack, pver, btcwire.MainNet}, + {msgGetAddr, msgGetAddr, pver, btcwire.MainNet}, + {msgAddr, msgAddr, pver, btcwire.MainNet}, + {msgGetBlocks, msgGetBlocks, pver, btcwire.MainNet}, + {msgBlock, msgBlock, pver, btcwire.MainNet}, + {msgInv, msgInv, pver, btcwire.MainNet}, + {msgGetData, msgGetData, pver, btcwire.MainNet}, + {msgNotFound, msgNotFound, pver, btcwire.MainNet}, + {msgTx, msgTx, pver, btcwire.MainNet}, + {msgPing, msgPing, pver, btcwire.MainNet}, + {msgPong, msgPong, pver, btcwire.MainNet}, + {msgGetHeaders, msgGetHeaders, pver, btcwire.MainNet}, + {msgHeaders, msgHeaders, pver, btcwire.MainNet}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := btcwire.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.NewBuffer(buf.Bytes()) + msg, _, err := btcwire.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 + } + } +} diff --git a/msgaddr.go b/msgaddr.go new file mode 100644 index 00000000..54eb4e6c --- /dev/null +++ b/msgaddr.go @@ -0,0 +1,136 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgAddr.AddAddress: too many addresses for message [max %v]" + return fmt.Errorf(str, MaxAddrPerMsg) + } + + 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 := "MsgAddr.BtcDecode: too many addresses in message [%v]" + return fmt.Errorf(str, 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 := "MsgAddr.BtcDecode: too many addresses in message " + + "for protocol version [version %v max 1]" + return fmt.Errorf(str, pver) + + } + if count > MaxAddrPerMsg { + str := "MsgAddr.BtcDecode: too many addresses in message [max %v]" + return fmt.Errorf(str, count) + } + + 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{} +} diff --git a/msgaddr_test.go b/msgaddr_test.go new file mode 100644 index 00000000..fd741ac5 --- /dev/null +++ b/msgaddr_test.go @@ -0,0 +1,205 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "net" + "reflect" + "testing" + "time" +) + +// TestAddr tests the MsgAddr API. +func TestAddr(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "addr" + msg := btcwire.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 := btcwire.NewNetAddress(tcpAddr, btcwire.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 < btcwire.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 = btcwire.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 = btcwire.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 := &btcwire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: btcwire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + na2 := &btcwire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: btcwire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8334, + } + + // Empty address message. + noAddr := btcwire.NewMsgAddr() + noAddrEncoded := []byte{ + 0x00, // Varint for number of addresses + } + + // Address message with multiple addresses. + multiAddr := btcwire.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 *btcwire.MsgAddr // Message to encode + out *btcwire.MsgAddr // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no addresses. + { + noAddr, + noAddr, + noAddrEncoded, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple addresses. + { + multiAddr, + multiAddr, + multiAddrEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version MultipleAddressVersion-1 with no addresses. + { + noAddr, + noAddr, + noAddrEncoded, + btcwire.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 btcwire.MsgAddr + 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 + } + } +} diff --git a/msgalert.go b/msgalert.go new file mode 100644 index 00000000..78879c12 --- /dev/null +++ b/msgalert.go @@ -0,0 +1,80 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "io" +) + +// 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 { + // PayloadBlob 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. + PayloadBlob string + + // Signature is the ECDSA signature of the message. + Signature string +} + +// 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.PayloadBlob, err = readVarString(r, pver) + if err != nil { + return err + } + msg.Signature, err = readVarString(r, pver) + 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 + err = writeVarString(w, pver, msg.PayloadBlob) + if err != nil { + return err + } + err = writeVarString(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(payloadblob string, signature string) *MsgAlert { + return &MsgAlert{ + PayloadBlob: payloadblob, + Signature: signature, + } +} diff --git a/msgalert_test.go b/msgalert_test.go new file mode 100644 index 00000000..b2b2d8c6 --- /dev/null +++ b/msgalert_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestAlert tests the MsgAlert API. +func TestAlert(t *testing.T) { + pver := btcwire.ProtocolVersion + payloadblob := "some message" + signature := "some sig" + + // Ensure we get the same payload and signature back out. + msg := btcwire.NewMsgAlert(payloadblob, signature) + if msg.PayloadBlob != payloadblob { + t.Errorf("NewMsgAlert: wrong payloadblob - got %v, want %v", + msg.PayloadBlob, payloadblob) + } + if 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) + } + + return +} + +// TestAlertWire tests the MsgAlert wire encode and decode for various protocol +// versions. +func TestAlertWire(t *testing.T) { + baseAlert := btcwire.NewMsgAlert("some payload", "somesig") + baseAlertEncoded := []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 *btcwire.MsgAlert // Message to encode + out *btcwire.MsgAlert // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseAlert, + baseAlert, + baseAlertEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + baseAlert, + baseAlert, + baseAlertEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseAlert, + baseAlert, + baseAlertEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseAlert, + baseAlert, + baseAlertEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseAlert, + baseAlert, + baseAlertEncoded, + btcwire.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 btcwire.MsgAlert + 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 + } + } +} diff --git a/msgblock.go b/msgblock.go new file mode 100644 index 00000000..cdf15eb2 --- /dev/null +++ b/msgblock.go @@ -0,0 +1,163 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "bytes" + "io" +) + +// MaxBlocksPerMsg is the maximum number of blocks allowed per message. +const MaxBlocksPerMsg = 500 + +// 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. +// +// NOTE: Unlike the other message types which contain slices, the number of +// transactions has a specific entry (Header.TxnCount) that must be kept in +// sync. The AddTransaction and ClearTransactions functions properly sync the +// value, but if you are manually modifying the public members, you will need +// to ensure you update the Header.TxnCount when you add and remove +// transactions. +type MsgBlock struct { + Header BlockHeader + Transactions []*MsgTx +} + +// AddTransaction adds a transaction to the message and updates Header.TxnCount +// accordingly. +func (msg *MsgBlock) AddTransaction(tx *MsgTx) error { + // TODO: Return error if adding the transaction would make the message + // too large. + msg.Transactions = append(msg.Transactions, tx) + msg.Header.TxnCount = uint64(len(msg.Transactions)) + return nil + +} + +// ClearTransactions removes all transactions from the message and updates +// Header.TxnCount accordingly. +func (msg *MsgBlock) ClearTransactions() { + msg.Transactions = []*MsgTx{} + msg.Header.TxnCount = 0 +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error { + err := readBlockHeader(r, pver, &msg.Header) + if err != nil { + return err + } + + for i := uint64(0); i < msg.Header.TxnCount; i++ { + tx := MsgTx{} + err := tx.BtcDecode(r, pver) + if err != nil { + return err + } + msg.Transactions = append(msg.Transactions, &tx) + } + + return nil +} + +// BtcDecodeTxLoc decodes r using the bitcoin protocol encoding into the +// receiver and returns a slice containing the start and length each transaction +// within the raw data. +func (msg *MsgBlock) BtcDecodeTxLoc(r *bytes.Buffer, pver uint32) ([]TxLoc, error) { + var fullLen int + fullLen = r.Len() + + err := readBlockHeader(r, pver, &msg.Header) + if err != nil { + return nil, err + } + + var txLocs []TxLoc + txLocs = make([]TxLoc, msg.Header.TxnCount) + + for i := uint64(0); i < msg.Header.TxnCount; i++ { + txLocs[i].TxStart = fullLen - r.Len() + tx := MsgTx{} + err := tx.BtcDecode(r, pver) + 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. +func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error { + // XXX: Max transactions? + msg.Header.TxnCount = uint64(len(msg.Transactions)) + + err := writeBlockHeader(w, pver, &msg.Header) + if err != nil { + return err + } + + for _, tx := range msg.Transactions { + err = tx.BtcEncode(w, pver) + 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 *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 81 bytes + max transactions which can vary up to the + // max message payload. + return maxMessagePayload +} + +// BlockSha computes the block identifier hash for this block. +func (msg *MsgBlock) BlockSha(pver uint32) (ShaHash, error) { + return msg.Header.BlockSha(pver) +} + +// TxShas returns a slice of hashes of all of transactions in this block. +func (msg *MsgBlock) TxShas(pver uint32) ([]ShaHash, error) { + var shaList []ShaHash + for _, tx := range msg.Transactions { + sha, err := tx.TxSha(pver) + if err != nil { + return nil, err + } + 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, + } +} diff --git a/msgblock_test.go b/msgblock_test.go new file mode 100644 index 00000000..5693153e --- /dev/null +++ b/msgblock_test.go @@ -0,0 +1,292 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" + "time" +) + +// TestBlock tests the MsgBlock API. +func TestBlock(t *testing.T) { + pver := uint32(60002) + + // Block 1 header. + prevHash := &blockOne.Header.PrevBlock + merkleHash := &blockOne.Header.MerkleRoot + bits := blockOne.Header.Bits + nonce := blockOne.Header.Nonce + bh := btcwire.NewBlockHeader(prevHash, merkleHash, bits, nonce) + + // Ensure the command is expected value. + wantCmd := "block" + msg := btcwire.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(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) + } + + // 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 +} + +func TestBlockTxShas(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Block 1, transaction 1 hash. + hashStr := "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" + wantHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + return + } + + wantShas := []btcwire.ShaHash{*wantHash} + shas, err := blockOne.TxShas(pver) + 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)) + } +} + +func TestBlockSha(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Block 1 hash. + hashStr := "839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" + wantHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the hash produced is expected. + blockHash, err := blockOne.BlockSha(pver) + 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 *btcwire.MsgBlock // Message to encode + out *btcwire.MsgBlock // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + &blockOne, + &blockOne, + blockOneBytes, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + &blockOne, + &blockOne, + blockOneBytes, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + &blockOne, + &blockOne, + blockOneBytes, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + &blockOne, + &blockOne, + blockOneBytes, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + &blockOne, + &blockOne, + blockOneBytes, + btcwire.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 btcwire.MsgBlock + 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 + } + } +} + +var blockOne btcwire.MsgBlock = btcwire.MsgBlock{ + Header: btcwire.BlockHeader{ + Version: 1, + PrevBlock: btcwire.ShaHash{ + 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: btcwire.ShaHash{ + 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 + TxnCount: 1, + }, + Transactions: []*btcwire.MsgTx{ + &btcwire.MsgTx{ + Version: 1, + TxIn: []*btcwire.TxIn{ + &btcwire.TxIn{ + PreviousOutpoint: btcwire.OutPoint{ + Hash: btcwire.ShaHash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*btcwire.TxOut{ + &btcwire.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, + }, + }, +} + +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 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 +} diff --git a/msggetaddr.go b/msggetaddr.go new file mode 100644 index 00000000..385a3e44 --- /dev/null +++ b/msggetaddr.go @@ -0,0 +1,47 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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/msggetaddr_test.go b/msggetaddr_test.go new file mode 100644 index 00000000..fdbc2d33 --- /dev/null +++ b/msggetaddr_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestGetAddr tests the MsgGetAddr API. +func TestGetAddr(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "getaddr" + msg := btcwire.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 := btcwire.NewMsgGetAddr() + msgGetAddrEncoded := []byte{} + + tests := []struct { + in *btcwire.MsgGetAddr // Message to encode + out *btcwire.MsgGetAddr // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + msgGetAddr, + msgGetAddr, + msgGetAddrEncoded, + btcwire.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 btcwire.MsgGetAddr + 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 + } + } +} diff --git a/msggetblocks.go b/msggetblocks.go new file mode 100644 index 00000000..9d301614 --- /dev/null +++ b/msggetblocks.go @@ -0,0 +1,140 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgGetBlocks.AddBlockLocatorHash: too many block " + + "locator hashes for message [max %v]" + return fmt.Errorf(str, MaxBlockLocatorsPerMsg) + } + + 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 := "%v: too many block locator hashes in message [%v]" + return fmt.Errorf(str, "MsgGetBlocks.BtcDecode", 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 := "%v: too many block locator hashes in message [%v]" + return fmt.Errorf(str, "MsgGetBlocks.BtcEncode", count) + } + + 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, + HashStop: *hashStop, + } +} diff --git a/msggetblocks_test.go b/msggetblocks_test.go new file mode 100644 index 00000000..8dc83b80 --- /dev/null +++ b/msggetblocks_test.go @@ -0,0 +1,259 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestGetBlocks tests the MsgGetBlocks API. +func TestGetBlocks(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Block 99500 hash. + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + locatorHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure we get the same data back out. + msg := btcwire.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 < btcwire.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 := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetBlocks message with no block locators or stop hash. + NoLocators := btcwire.NewMsgGetBlocks(&btcwire.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 := btcwire.NewMsgGetBlocks(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 *btcwire.MsgGetBlocks // Message to encode + out *btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Versionwith multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.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 btcwire.MsgGetBlocks + 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 + } + } +} diff --git a/msggetdata.go b/msggetdata.go new file mode 100644 index 00000000..865c94d4 --- /dev/null +++ b/msggetdata.go @@ -0,0 +1,105 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgAddr.AddAddress: too many invvect in message [max %v]" + return fmt.Errorf(str, MaxInvPerMsg) + } + + 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 := "MsgGetData.BtcDecode: too many invvect in message [%v]" + return fmt.Errorf(str, 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 := "MsgGetData.BtcDecode: too many invvect in message [%v]" + return fmt.Errorf(str, count) + } + + 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{} +} diff --git a/msggetdata_test.go b/msggetdata_test.go new file mode 100644 index 00000000..b800af7c --- /dev/null +++ b/msggetdata_test.go @@ -0,0 +1,222 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestGetData tests the MsgGetData API. +func TestGetData(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "getdata" + msg := btcwire.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 := btcwire.ShaHash{} + iv := btcwire.NewInvVect(btcwire.InvVect_Block, &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 < btcwire.MaxInvPerMsg; i++ { + err = msg.AddInvVect(iv) + } + if err == nil { + t.Errorf("AddInvVect: expected error on too many inventory " + + "vectors not received") + } + + 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 := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash) + iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash) + + // Empty MsgGetData message. + NoInv := btcwire.NewMsgGetData() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // MsgGetData message with multiple inventory vectors. + MultiInv := btcwire.NewMsgGetData() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvVect_Block + 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, // InvVect_Tx + 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 *btcwire.MsgGetData // Message to encode + out *btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.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 btcwire.MsgGetData + 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 + } + } +} diff --git a/msggetheaders.go b/msggetheaders.go new file mode 100644 index 00000000..0caeaf83 --- /dev/null +++ b/msggetheaders.go @@ -0,0 +1,135 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgGetHeaders.AddBlockLocatorHash: too many block " + + "locator hashes for message [max %v]" + return fmt.Errorf(str, MaxBlockLocatorsPerMsg) + } + + 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 := "%v: too many block locator hashes in message [%v]" + return fmt.Errorf(str, "MsgGetHeaders.BtcDecode", 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 := "MsgGetHeaders.BtcEncode: too many block locator " + + "hashes in message [%v]" + return fmt.Errorf(str, count) + } + + 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{} +} diff --git a/msggetheaders_test.go b/msggetheaders_test.go new file mode 100644 index 00000000..86a0f8f5 --- /dev/null +++ b/msggetheaders_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestGetHeaders tests the MsgGetHeader API. +func TestGetHeaders(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Block 99500 hash. + hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + locatorHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the command is expected value. + wantCmd := "getheaders" + msg := btcwire.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 < btcwire.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. + pver := uint32(60002) + + // Block 99499 hash. + hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535" + hashLocator, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 99500 hash. + hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0" + hashLocator2, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Block 100000 hash. + hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hashStop, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // MsgGetHeaders message with no block locators or stop hash. + NoLocators := btcwire.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 := btcwire.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 *btcwire.MsgGetHeaders // Message to encode + out *btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Versionwith multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no block locators. + { + NoLocators, + NoLocators, + NoLocatorsEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion multiple block locators. + { + MultiLocators, + MultiLocators, + MultiLocatorsEncoded, + btcwire.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 btcwire.MsgGetHeaders + 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 + } + } +} diff --git a/msgheaders.go b/msgheaders.go new file mode 100644 index 00000000..dd416651 --- /dev/null +++ b/msgheaders.go @@ -0,0 +1,119 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgHeaders.AddBlockHeader: too many block headers " + + "for message [max %v]" + return fmt.Errorf(str, MaxBlockHeadersPerMsg) + } + + 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 := "MsgHeaders.BtcDecode: too many block headers in message [%v]" + return fmt.Errorf(str, count) + } + + for i := uint64(0); i < count; i++ { + bh := BlockHeader{} + err := readBlockHeader(r, pver, &bh) + if err != nil { + return err + } + + // Ensure the transaction count is zero for headers. + if bh.TxnCount > 0 { + str := "MsgHeaders.BtcDecode: block headers may not " + + "contain transactions [%v]" + return fmt.Errorf(str, count) + } + 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 := "MsgHeaders.BtcEncode: too many block headers in message [%v]" + return fmt.Errorf(str, count) + } + + err := writeVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, bh := range msg.Headers { + // Ensure block headers do not contain a transaction count. + if bh.TxnCount > 0 { + str := "MsgHeaders.BtcEncode: block headers " + + "may not contain transactions [%v]" + return fmt.Errorf(str, count) + } + + err := writeBlockHeader(w, pver, bh) + 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. + return maxVarIntPayload + (maxBlockHeaderPayload * MaxBlockHeadersPerMsg) +} + +// NewMsgGetHeaders returns a new bitcoin headers message that conforms to the +// Message interface. See MsgHeaders for details. +func NewMsgHeaders() *MsgHeaders { + return &MsgHeaders{} +} diff --git a/msgheaders_test.go b/msgheaders_test.go new file mode 100644 index 00000000..67ac3c89 --- /dev/null +++ b/msgheaders_test.go @@ -0,0 +1,219 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +var dumpmessage bool = false +var checkverbose = true + +const time1 uint32 = 1363020888 +const time2 uint32 = 1363021079 + +// TestHeaders tests the MsgHeaders API. +func TestHeaders(t *testing.T) { + pver := uint32(60002) + + // Ensure the command is expected value. + wantCmd := "headers" + msg := btcwire.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. + wantPayload := uint32(178009) + 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 < btcwire.MaxBlockHeadersPerMsg+1; i++ { + err = msg.AddBlockHeader(bh) + } + // TODO(davec): Check for actual error. + if err == nil { + 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 := btcwire.GenesisHash + merkleHash := blockOne.Header.MerkleRoot + bits := uint32(0x1d00ffff) + nonce := uint32(0x9962e301) + bh := btcwire.NewBlockHeader(&hash, &merkleHash, bits, nonce) + bh.Version = blockOne.Header.Version + bh.Timestamp = blockOne.Header.Timestamp + + // Empty headers message. + noHeaders := btcwire.NewMsgHeaders() + noHeadersEncoded := []byte{ + 0x00, // Varint for number of headers + } + + // Headers message with one header. + oneHeader := btcwire.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 *btcwire.MsgHeaders // Message to encode + out *btcwire.MsgHeaders // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + btcwire.BIP0031Version, + }, + // Protocol version NetAddressTimeVersion with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no headers. + { + noHeaders, + noHeaders, + noHeadersEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with one header. + { + oneHeader, + oneHeader, + oneHeaderEncoded, + btcwire.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 btcwire.MsgHeaders + 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 + } + } +} diff --git a/msginv.go b/msginv.go new file mode 100644 index 00000000..9cbb5bf1 --- /dev/null +++ b/msginv.go @@ -0,0 +1,104 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "fmt" + "io" +) + +// 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 := "MsgInv.AddAddress: too many invvect in message [max %v]" + return fmt.Errorf(str, MaxInvPerMsg) + } + + 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 := "MsgInv.BtcDecode: too many invvect in message [%v]" + return fmt.Errorf(str, 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 := "MsgInv.BtcEncode: too many invvect in message [%v]" + return fmt.Errorf(str, count) + } + + 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{} +} diff --git a/msginv_test.go b/msginv_test.go new file mode 100644 index 00000000..9852ed2b --- /dev/null +++ b/msginv_test.go @@ -0,0 +1,222 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestInv tests the MsgInv API. +func TestInv(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "inv" + msg := btcwire.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 := btcwire.ShaHash{} + iv := btcwire.NewInvVect(btcwire.InvVect_Block, &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 < btcwire.MaxInvPerMsg; i++ { + err = msg.AddInvVect(iv) + } + if err == nil { + t.Errorf("AddInvVect: expected error on too many inventory " + + "vectors not received") + } + + 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 := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash) + iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash) + + // Empty inv message. + NoInv := btcwire.NewMsgInv() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // Inv message with multiple inventory vectors. + MultiInv := btcwire.NewMsgInv() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvVect_Block + 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, // InvVect_Tx + 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 *btcwire.MsgInv // Message to encode + out *btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.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 btcwire.MsgInv + 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 + } + } +} diff --git a/msgmempool.go b/msgmempool.go new file mode 100644 index 00000000..36eaf7f5 --- /dev/null +++ b/msgmempool.go @@ -0,0 +1,58 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 { + err := fmt.Errorf("mempool message invalid for protocol version: %d", pver) + 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 *MsgMemPool) BtcEncode(w io.Writer, pver uint32) error { + if pver < BIP0035Version { + err := fmt.Errorf("mempool message invalid for protocol version: %d", pver) + return err + } + + 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 +} + +// NewMsgPong returns a new bitcoin pong message that conforms to the Message +// interface. See MsgPong for details. +func NewMsgMemPool() *MsgMemPool { + return &MsgMemPool{} +} diff --git a/msgmempool_test.go b/msgmempool_test.go new file mode 100644 index 00000000..4cab821e --- /dev/null +++ b/msgmempool_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "testing" +) + +func TestMemPool(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "mempool" + msg := btcwire.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 := btcwire.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 := btcwire.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/msgnotfound.go b/msgnotfound.go new file mode 100644 index 00000000..c920fe33 --- /dev/null +++ b/msgnotfound.go @@ -0,0 +1,103 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 := "MsgNotFound.AddAddress: too many invvect in message [max %v]" + return fmt.Errorf(str, MaxInvPerMsg) + } + + 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 := "MsgNotFound.BtcDecode: too many invvect in message [%v]" + return fmt.Errorf(str, 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 := "MsgNotFound.BtcEncode: too many invvect in message [%v]" + return fmt.Errorf(str, count) + } + + 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{} +} diff --git a/msgnotfound_test.go b/msgnotfound_test.go new file mode 100644 index 00000000..190f2da2 --- /dev/null +++ b/msgnotfound_test.go @@ -0,0 +1,222 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestNotFound tests the MsgNotFound API. +func TestNotFound(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "notfound" + msg := btcwire.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 := btcwire.ShaHash{} + iv := btcwire.NewInvVect(btcwire.InvVect_Block, &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 < btcwire.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 := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Transation 1 of Block 203707 hash. + hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" + txHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash) + iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash) + + // Empty notfound message. + NoInv := btcwire.NewMsgNotFound() + NoInvEncoded := []byte{ + 0x00, // Varint for number of inventory vectors + } + + // NotFound message with multiple inventory vectors. + MultiInv := btcwire.NewMsgNotFound() + MultiInv.AddInvVect(iv) + MultiInv.AddInvVect(iv2) + MultiInvEncoded := []byte{ + 0x02, // Varint for number of inv vectors + 0x02, 0x00, 0x00, 0x00, // InvVect_Block + 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, // InvVect_Tx + 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 *btcwire.MsgNotFound // Message to encode + out *btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion no inv vectors. + { + NoInv, + NoInv, + NoInvEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple inv vectors. + { + MultiInv, + MultiInv, + MultiInvEncoded, + btcwire.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 btcwire.MsgNotFound + 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 + } + } +} diff --git a/msgping.go b/msgping.go new file mode 100644 index 00000000..15f6580d --- /dev/null +++ b/msgping.go @@ -0,0 +1,87 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "io" +) + +// MsgPong 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/msgping_test.go b/msgping_test.go new file mode 100644 index 00000000..8f0d9d18 --- /dev/null +++ b/msgping_test.go @@ -0,0 +1,194 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestPing tests the MsgPing API against the latest protocol version. +func TestPing(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure we get the same nonce back out. + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := btcwire.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 := btcwire.BIP0031Version + + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := btcwire.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 := btcwire.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 decoded with BIP0031Version. +func TestPingCrossProtocol(t *testing.T) { + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: Error generating nonce: %v", err) + } + msg := btcwire.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, btcwire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgPing failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + readmsg := btcwire.NewMsgPing(0) + err = readmsg.BtcDecode(&buf, btcwire.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 btcwire.MsgPing // Message to encode + out btcwire.MsgPing // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + btcwire.MsgPing{Nonce: 123123}, // 0x1e0f3 + btcwire.MsgPing{Nonce: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0031Version+1 + { + btcwire.MsgPing{Nonce: 456456}, // 0x6f708 + btcwire.MsgPing{Nonce: 456456}, // 0x6f708 + []byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + btcwire.BIP0031Version + 1, + }, + + // Protocol version BIP0031Version + { + btcwire.MsgPing{Nonce: 789789}, // 0xc0d1d + btcwire.MsgPing{Nonce: 0}, // No nonce for pver + []byte{}, // No nonce for pver + btcwire.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 btcwire.MsgPing + 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 + } + } +} diff --git a/msgpong.go b/msgpong.go new file mode 100644 index 00000000..8794ebd5 --- /dev/null +++ b/msgpong.go @@ -0,0 +1,88 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 { + err := fmt.Errorf("pong message invalid for protocol version: %d", + pver) + return err + } + + 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 { + err := fmt.Errorf("pong message invalid for protocol version: %d", + pver) + return err + } + + 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/msgpong_test.go b/msgpong_test.go new file mode 100644 index 00000000..0297888d --- /dev/null +++ b/msgpong_test.go @@ -0,0 +1,215 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestPongLatest tests the MsgPong API against the latest protocol version. +func TestPongLatest(t *testing.T) { + pver := btcwire.ProtocolVersion + + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: error generating nonce: %v", err) + } + msg := btcwire.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 := btcwire.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 := btcwire.BIP0031Version + + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("Error generating nonce: %v", err) + } + msg := btcwire.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 := btcwire.NewMsgPong(0) + err = readmsg.BtcDecode(&buf, pver) + if err == nil { + t.Errorf("decode of MsgPong succeeded when it shouldn't have", + 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 decoded with BIP0031Version. +func TestPongCrossProtocol(t *testing.T) { + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("Error generating nonce: %v", err) + } + msg := btcwire.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, btcwire.ProtocolVersion) + if err != nil { + t.Errorf("encode of MsgPong failed %v err <%v>", msg, err) + } + + // Decode with old protocol version. + readmsg := btcwire.NewMsgPong(0) + err = readmsg.BtcDecode(&buf, btcwire.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 btcwire.MsgPong // Message to encode + out btcwire.MsgPong // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + err error // expected error + }{ + // Latest protocol version. + { + btcwire.MsgPong{Nonce: 123123}, // 0x1e0f3 + btcwire.MsgPong{Nonce: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + btcwire.ProtocolVersion, + nil, + }, + + // Protocol version BIP0031Version+1 + { + btcwire.MsgPong{Nonce: 456456}, // 0x6f708 + btcwire.MsgPong{Nonce: 456456}, // 0x6f708 + []byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + btcwire.BIP0031Version + 1, + nil, + }, + + // Protocol version BIP0031Version + //{ + // btcwire.MsgPong{Nonce: 789789}, // 0xc0d1d + // btcwire.MsgPong{Nonce: 0}, // No nonce for pver + // []byte{}, // No nonce for pver + // btcwire.BIP0031Version, + // nil, /// Need err type.... + //}, + } + + 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 != test.err { + 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 btcwire.MsgPong + rbuf := bytes.NewBuffer(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != test.err { + 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/msgtx.go b/msgtx.go new file mode 100644 index 00000000..27110f0c --- /dev/null +++ b/msgtx.go @@ -0,0 +1,387 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "bytes" + "io" +) + +// MaxTxInSequenceNum is the maximum sequence number the sequence field +// of a transaction input can be. +const MaxTxInSequenceNum uint32 = 0xffffffff + +// 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, + } +} + +// TxIn defines a bitcoin transaction input. +type TxIn struct { + PreviousOutpoint OutPoint + SignatureScript []byte + Sequence uint32 +} + +// 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 +} + +// 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 uint32 + 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 (tx *MsgTx) TxSha(pver uint32) (ShaHash, error) { + var txsha ShaHash + var wbuf bytes.Buffer + err := tx.BtcEncode(&wbuf, pver) + if err != nil { + return txsha, err + } + txsha.SetBytes(DoubleSha256(wbuf.Bytes())) + + return txsha, nil +} + +// Copy creates a deep copy of a transaction so that the original does not get +// modified when the copy is manipulated. +func (tx *MsgTx) Copy() *MsgTx { + // Create new tx and start by copying primitive values. + newTx := MsgTx{ + Version: tx.Version, + LockTime: tx.LockTime, + } + + // Deep copy the old TxIn data. + for _, oldTxIn := range tx.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 tx.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. +func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error { + err := readElement(r, &msg.Version) + if err != nil { + return err + } + + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + for i := uint64(0); i < count; i++ { + ti := TxIn{} + err = readTxIn(r, pver, msg.Version, &ti) + if err != nil { + return err + } + msg.TxIn = append(msg.TxIn, &ti) + } + + count, err = readVarInt(r, pver) + if err != nil { + return err + } + + for i := uint64(0); i < count; i++ { + to := TxOut{} + err = readTxOut(r, pver, msg.Version, &to) + if err != nil { + return err + } + msg.TxOut = append(msg.TxOut, &to) + } + + err = readElement(r, &msg.LockTime) + 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 *MsgTx) BtcEncode(w io.Writer, pver uint32) error { + err := writeElement(w, msg.Version) + 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, to) + if err != nil { + return err + } + } + + err = writeElement(w, msg.LockTime) + 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 *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 maxMessagePayload +} + +// 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} +} + +// readOutPoint reads the next sequence of bytes from r as an OutPoint. +func readOutPoint(r io.Reader, pver uint32, version uint32, op *OutPoint) error { + err := readElements(r, &op.Hash, &op.Index) + if err != nil { + return err + } + return nil +} + +// writeOutPoint encodes op to the bitcoin protocol encoding for an OutPoint +// to w. +func writeOutPoint(w io.Writer, pver uint32, version uint32, op *OutPoint) error { + err := writeElements(w, op.Hash, op.Index) + 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 uint32, ti *TxIn) error { + op := OutPoint{} + err := readOutPoint(r, pver, version, &op) + if err != nil { + return err + } + ti.PreviousOutpoint = op + + count, err := readVarInt(r, pver) + if err != nil { + return err + } + + b := make([]byte, count) + err = readElement(r, b) + if err != nil { + return err + } + ti.SignatureScript = b + + err = readElement(r, &ti.Sequence) + if err != nil { + return err + } + + 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 uint32, ti *TxIn) error { + err := writeOutPoint(w, pver, version, &ti.PreviousOutpoint) + if err != nil { + return err + } + + slen := uint64(len(ti.SignatureScript)) + err = writeVarInt(w, pver, slen) + if err != nil { + return err + } + + b := []byte(ti.SignatureScript) + _, err = w.Write(b) + if err != nil { + return err + } + + err = writeElement(w, &ti.Sequence) + 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 uint32, to *TxOut) error { + err := readElement(r, &to.Value) + if err != nil { + return err + } + + slen, err := readVarInt(r, pver) + if err != nil { + return err + } + + b := make([]byte, slen) + err = readElement(r, b) + if err != nil { + return err + } + to.PkScript = b + + return nil +} + +// writeTxOut encodes to into the bitcoin protocol encoding for a transaction +// output (TxOut) to w. +func writeTxOut(w io.Writer, pver uint32, to *TxOut) error { + err := writeElement(w, to.Value) + if err != nil { + return err + } + + pkLen := uint64(len(to.PkScript)) + err = writeVarInt(w, pver, pkLen) + if err != nil { + return err + } + + err = writeElement(w, to.PkScript) + if err != nil { + return err + } + + return nil +} diff --git a/msgtx_test.go b/msgtx_test.go new file mode 100644 index 00000000..66449bd5 --- /dev/null +++ b/msgtx_test.go @@ -0,0 +1,361 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestTx tests the MsgTx API. +func TestTx(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Block 100000 hash. + hashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + } + + // Ensure the command is expected value. + wantCmd := "tx" + msg := btcwire.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(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) + } + + // Ensure we get the same transaction output point data back out. + prevOutIndex := uint32(1) + prevOut := btcwire.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) + } + + // Ensure we get the same transaction input back out. + sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62} + txIn := btcwire.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 := btcwire.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 +} + +func TestTxSha(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Hash of first transaction from block 113875. + hashStr := "f051e59b5e2503ac626d03aaeac8ab7be2d72ba4b7e97119c5852d70d52dcb86" + wantHash, err := btcwire.NewShaHashFromStr(hashStr) + if err != nil { + t.Errorf("NewShaHashFromStr: %v", err) + return + } + + // First transaction from block 113875. + msgTx := btcwire.NewMsgTx() + txIn := btcwire.TxIn{ + PreviousOutpoint: btcwire.OutPoint{ + Hash: btcwire.ShaHash{0x00}, + Index: 0xffffffff, + }, + SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}, + Sequence: 0xffffffff, + } + txOut := btcwire.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(pver) + 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) { + // Previous transaction output point for coinbase to test. + prevOutIndex := uint32(0xffffffff) + prevOut := btcwire.NewOutPoint(&btcwire.ShaHash{}, prevOutIndex) + + // Transaction input to test. + sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62} + txIn := btcwire.NewTxIn(prevOut, sigScript) + txIn.Sequence = 0xffffffff + + // Transaction output to test. + 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 := btcwire.NewTxOut(txValue, pkScript) + + // Empty tx message. + noTx := btcwire.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 + } + + multiTx := btcwire.NewMsgTx() + multiTx.Version = 1 + multiTx.AddTxIn(txIn) + multiTx.AddTxOut(txOut) + multiTx.LockTime = 0 + 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 + } + + tests := []struct { + in *btcwire.MsgTx // Message to encode + out *btcwire.MsgTx // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version with no transactions. + { + noTx, + noTx, + noTxEncoded, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version with no transactions. + { + noTx, + noTx, + noTxEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0035Version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version with no transactions. + { + noTx, + noTx, + noTxEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version BIP0031Version with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion with no transactions. + { + noTx, + noTx, + noTxEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion with no transactions. + { + noTx, + noTx, + noTxEncoded, + btcwire.MultipleAddressVersion, + }, + + // Protocol version MultipleAddressVersion with multiple transactions. + { + multiTx, + multiTx, + multiTxEncoded, + btcwire.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 btcwire.MsgTx + 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 + } + } +} diff --git a/msgverack.go b/msgverack.go new file mode 100644 index 00000000..65ba8afc --- /dev/null +++ b/msgverack.go @@ -0,0 +1,46 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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/msgverack_test.go b/msgverack_test.go new file mode 100644 index 00000000..a32acd19 --- /dev/null +++ b/msgverack_test.go @@ -0,0 +1,121 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +// TestVerAck tests the MsgVerAck API. +func TestVerAck(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Ensure the command is expected value. + wantCmd := "verack" + msg := btcwire.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 := btcwire.NewMsgVerAck() + msgVerAckEncoded := []byte{} + + tests := []struct { + in *btcwire.MsgVerAck // Message to encode + out *btcwire.MsgVerAck // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + msgVerAck, + msgVerAck, + msgVerAckEncoded, + btcwire.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 btcwire.MsgVerAck + 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 + } + } +} diff --git a/msgversion.go b/msgversion.go new file mode 100644 index 00000000..d9b13e37 --- /dev/null +++ b/msgversion.go @@ -0,0 +1,210 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "fmt" + "io" + "net" + "time" +) + +// MaxUserAgentLen is the maximum allowed length for the user agent field in a +// version message (MsgVersion). +const MaxUserAgentLen = 2000 + +// 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 +} + +// 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. +// This is part of the Message interface implementation. +func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error { + var sec int64 + err := readElements(r, &msg.ProtocolVersion, &msg.Services, &sec) + if err != nil { + return err + } + msg.Timestamp = time.Unix(sec, 0) + + err = readNetAddress(r, pver, &msg.AddrYou, false) + if err != nil { + return err + } + + err = readNetAddress(r, pver, &msg.AddrMe, false) + if err != nil { + return err + } + + err = readElement(r, &msg.Nonce) + if err != nil { + return err + } + + userAgent, err := readVarString(r, pver) + if err != nil { + return err + } + if len(userAgent) > MaxUserAgentLen { + str := "MsgVersion.BtcDecode: user agent too long [max %v]" + return fmt.Errorf(str, MaxUserAgentLen) + } + msg.UserAgent = userAgent + + err = readElement(r, &msg.LastBlock) + 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 *MsgVersion) BtcEncode(w io.Writer, pver uint32) error { + if len(msg.UserAgent) > MaxUserAgentLen { + str := "MsgVersion.BtcEncode: user agent too long [max %v]" + return fmt.Errorf(str, MaxUserAgentLen) + } + + 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 + } + + 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 + // XXX: >= 70001 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. + return 32 + (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, + userAgent string, lastBlock int32) *MsgVersion { + + // Limit the Timestamp to millisecond 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: userAgent, + LastBlock: lastBlock, + } +} + +// 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, userAgent string, + 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, userAgent, lastBlock), nil +} diff --git a/msgversion_test.go b/msgversion_test.go new file mode 100644 index 00000000..f9071b84 --- /dev/null +++ b/msgversion_test.go @@ -0,0 +1,277 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "net" + "reflect" + "testing" + "time" +) + +// TestVersion tests the MsgVersion API. +func TestVersion(t *testing.T) { + pver := btcwire.ProtocolVersion + + // Create version message data. + userAgent := "/btcdtest:0.0.1/" + lastBlock := int32(234234) + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} + me, err := btcwire.NewNetAddress(tcpAddrMe, btcwire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + tcpAddrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} + you, err := btcwire.NewNetAddress(tcpAddrYou, btcwire.SFNodeNetwork) + if err != nil { + t.Errorf("NewNetAddress: %v", err) + } + nonce, err := btcwire.RandomUint64() + if err != nil { + t.Errorf("RandomUint64: error generating nonce: %v", err) + } + + // Ensure we get the correct data back out. + msg := btcwire.NewMsgVersion(me, you, nonce, userAgent, 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 != userAgent { + t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v", + msg.UserAgent, userAgent) + } + if msg.LastBlock != lastBlock { + t.Errorf("NewMsgVersion: wrong last block - got %v, want %v", + msg.LastBlock, lastBlock) + } + + // 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(btcwire.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. + wantPayload := uint32(2101) + 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(btcwire.SFNodeNetwork) + if msg.Services != btcwire.SFNodeNetwork { + t.Errorf("AddService: wrong services - got %v, want %v", + msg.Services, btcwire.SFNodeNetwork) + } + if !msg.HasService(btcwire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service not set") + } + + // Use a fake connection. + conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou} + msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, 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 = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock) + if err != btcwire.ErrInvalidNetAddr { + t.Errorf("NewMsgVersionFromConn: expected error not received "+ + "- got %v, want %v", err, btcwire.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 = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock) + if err != btcwire.ErrInvalidNetAddr { + t.Errorf("NewMsgVersionFromConn: expected error not received "+ + "- got %v, want %v", err, btcwire.ErrInvalidNetAddr) + } + + return +} + +// TestAlertWire tests the MsgAlert wire encode and decode for various protocol +// versions. +func TestVersionWire(t *testing.T) { + // baseNetAddrYou is used in the various tests as a baseline remote + // NetAddress. + baseNetAddrYou := btcwire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: btcwire.SFNodeNetwork, + IP: net.ParseIP("192.168.0.1"), + Port: 8333, + } + + // baseNetAddrMe is used in the various tests as a baseline local + // NetAddress. + baseNetAddrMe := btcwire.NetAddress{ + Timestamp: time.Time{}, // Zero value -- no timestamp in version + Services: btcwire.SFNodeNetwork, + IP: net.ParseIP("127.0.0.1"), + Port: 8333, + } + + // baseVersion is used in the various tests as a baseline version. + baseVersion := &btcwire.MsgVersion{ + ProtocolVersion: 60002, + Services: btcwire.SFNodeNetwork, + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST) + AddrYou: baseNetAddrYou, + AddrMe: baseNetAddrMe, + Nonce: 123123, // 0x1e0f3 + UserAgent: "/btcdtest:0.0.1/", + LastBlock: 234234, // 0x392fa + } + + 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 + } + + tests := []struct { + in *btcwire.MsgVersion // Message to encode + out *btcwire.MsgVersion // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + baseVersion, + baseVersion, + baseVersionEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version BIP0035Version. + { + baseVersion, + baseVersion, + baseVersionEncoded, + btcwire.BIP0035Version, + }, + + // Protocol version BIP0031Version. + { + baseVersion, + baseVersion, + baseVersionEncoded, + btcwire.BIP0031Version, + }, + + // Protocol version NetAddressTimeVersion. + { + baseVersion, + baseVersion, + baseVersionEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version MultipleAddressVersion. + { + baseVersion, + baseVersion, + baseVersionEncoded, + btcwire.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 btcwire.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 + } + } +} diff --git a/netaddress.go b/netaddress.go new file mode 100644 index 00000000..b8db7468 --- /dev/null +++ b/netaddress.go @@ -0,0 +1,163 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +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 +} + +// 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 := NetAddress{ + Timestamp: time.Now(), + Services: services, + IP: tcpAddr.IP, + Port: uint16(tcpAddr.Port), + } + 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/netaddress_test.go b/netaddress_test.go new file mode 100644 index 00000000..692af3c9 --- /dev/null +++ b/netaddress_test.go @@ -0,0 +1,215 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "github.com/conformal/btcwire" + "github.com/davecgh/go-spew/spew" + "net" + "reflect" + "testing" + "time" +) + +// 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 := btcwire.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(btcwire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service is set") + } + + // Ensure adding the full service node flag works. + na.AddService(btcwire.SFNodeNetwork) + if na.Services != btcwire.SFNodeNetwork { + t.Errorf("AddService: wrong services - got %v, want %v", + na.Services, btcwire.SFNodeNetwork) + } + if !na.HasService(btcwire.SFNodeNetwork) { + t.Errorf("HasService: SFNodeNetwork service not set") + } + + // Ensure max payload is expected value for latest protocol version. + pver := btcwire.ProtocolVersion + wantPayload := uint32(30) + maxPayload := btcwire.TstMaxNetAddressPayload(btcwire.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 = btcwire.NetAddressTimeVersion - 1 + wantPayload = 26 + maxPayload = btcwire.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 = btcwire.NewNetAddress(udpAddr, 0) + if err != btcwire.ErrInvalidNetAddr { + t.Errorf("NewNetAddress: expected error not received - "+ + "got %v, want %v", err, btcwire.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 := btcwire.NetAddress{ + Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST + Services: btcwire.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 btcwire.NetAddress // NetAddress to encode + out btcwire.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, + btcwire.ProtocolVersion, + }, + + // Latest protocol version with ts flag. + { + baseNetAddr, + baseNetAddr, + true, + baseNetAddrEncoded, + btcwire.ProtocolVersion, + }, + + // Protocol version NetAddressTimeVersion without ts flag. + { + baseNetAddr, + baseNetAddrNoTS, + false, + baseNetAddrNoTSEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion with ts flag. + { + baseNetAddr, + baseNetAddr, + true, + baseNetAddrEncoded, + btcwire.NetAddressTimeVersion, + }, + + // Protocol version NetAddressTimeVersion-1 without ts flag. + { + baseNetAddr, + baseNetAddrNoTS, + false, + baseNetAddrNoTSEncoded, + btcwire.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, + btcwire.NetAddressTimeVersion - 1, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + var buf bytes.Buffer + err := btcwire.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 btcwire.NetAddress + rbuf := bytes.NewBuffer(test.buf) + err = btcwire.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 + } + } +} diff --git a/protocol.go b/protocol.go new file mode 100644 index 00000000..7fe9f601 --- /dev/null +++ b/protocol.go @@ -0,0 +1,83 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "strconv" + "strings" +) + +const ( + MainPort = "8333" + TestNetPort = "18333" + ProtocolVersion uint32 = 60002 + TxVersion = 1 + + // 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 +) + +// ServiceFlag identifies services supported by a bitcoin peer. +type ServiceFlag uint64 + +const ( + 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 BitcoinNet = 0xd9b4bef9 + TestNet BitcoinNet = 0xdab5bffa + TestNet3 BitcoinNet = 0x0709110b +) diff --git a/protocol_test.go b/protocol_test.go new file mode 100644 index 00000000..b95515c2 --- /dev/null +++ b/protocol_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "github.com/conformal/btcwire" + "testing" +) + +// TestServiceFlagStringer tests the stringized output for service flag types. +func TestServiceFlagStringer(t *testing.T) { + tests := []struct { + in btcwire.ServiceFlag + want string + }{ + {0, "0x0"}, + {btcwire.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 + } + } +} diff --git a/shahash.go b/shahash.go new file mode 100644 index 00000000..2e21439c --- /dev/null +++ b/shahash.go @@ -0,0 +1,106 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "bytes" + "encoding/hex" + "fmt" +) + +// Size of array used to store sha hashes. See ShaHash. +const HashSize = 32 +const MaxHashStringSize = HashSize * 2 + +var ErrHashStrSize = fmt.Errorf("Max hash length is %v chars", 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 in the standard bitcoin big-endian form. +func (hash *ShaHash) String() string { + hashstr := "" + for i := range hash { + hashstr += fmt.Sprintf("%02x", hash[HashSize-1-i]) + } + + return hashstr +} + +// 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("ShaHash: 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 converts a hash string in the standard bitcoin big-endian +// form to a ShaHash (which is little-endian). +func NewShaHashFromStr(hash string) (*ShaHash, error) { + // Return error is 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 + } + + // The string was given in big-endian, so reverse the bytes to little + // endian. + blen := len(buf) + for i := 0; i < blen/2; i++ { + buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] + } + + // Make sure the byte slice is the right length by appending zeros to + // pad it out. + pbuf := buf + if HashSize-blen > 0 { + pbuf = make([]byte, HashSize) + copy(pbuf, buf) + } + + // Create the sha hash using the byte slice and return it. + return NewShaHash(pbuf) +} diff --git a/shahash_test.go b/shahash_test.go new file mode 100644 index 00000000..ea143507 --- /dev/null +++ b/shahash_test.go @@ -0,0 +1,167 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire_test + +import ( + "bytes" + "encoding/hex" + "github.com/conformal/btcwire" + "testing" +) + +// TestShaHash tests the ShaHash API. +func TestShaHash(t *testing.T) { + + // Hash of block 234439. + blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" + blockHash, err := btcwire.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 := btcwire.NewShaHash(buf) + if err != nil { + t.Errorf("NewShaHash: unexpected error %v", err) + } + + // Ensure proper size. + if len(hash) != btcwire.HashSize { + t.Errorf("NewShaHash: hash length mismatch - got: %v, want: %v", + len(hash), btcwire.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") + } +} + +// TestShaHashString tests the stringized output for sha hashes. +func TestShaHashString(t *testing.T) { + // Block 100000 hash. + wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506" + hash := &btcwire.ShaHash{ + 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 btcwire.ShaHash + err error + }{ + // Genesis hash. + { + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + btcwire.GenesisHash, + nil, + }, + + // Genesis hash with stripped leading zeros. + { + "19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + btcwire.GenesisHash, + nil, + }, + + // Single digit hash. + { + "1", + btcwire.ShaHash{ + 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", + btcwire.ShaHash{ + 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", + btcwire.ShaHash{}, + btcwire.ErrHashStrSize, + }, + + // Hash string that is contains non-hex chars. + { + "abcdefg", + btcwire.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 := btcwire.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 + } + } +} diff --git a/test_coverage.txt b/test_coverage.txt new file mode 100644 index 00000000..9e8a7a68 --- /dev/null +++ b/test_coverage.txt @@ -0,0 +1,152 @@ + +github.com/conformal/btcwire/msgtx.go MsgTx.Copy 100.00% (24/24) +github.com/conformal/btcwire/common.go readVarInt 100.00% (24/24) +github.com/conformal/btcwire/common.go writeVarInt 100.00% (16/16) +github.com/conformal/btcwire/shahash.go NewShaHashFromStr 100.00% (15/15) +github.com/conformal/btcwire/protocol.go ServiceFlag.String 100.00% (12/12) +github.com/conformal/btcwire/common.go readVarString 100.00% (8/8) +github.com/conformal/btcwire/common.go writeVarString 100.00% (7/7) +github.com/conformal/btcwire/common.go DoubleSha256 100.00% (7/7) +github.com/conformal/btcwire/common.go randomUint64 100.00% (7/7) +github.com/conformal/btcwire/msgversion.go NewMsgVersionFromConn 100.00% (7/7) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.AddBlockHeader 100.00% (5/5) +github.com/conformal/btcwire/msgaddr.go MsgAddr.AddAddress 100.00% (5/5) +github.com/conformal/btcwire/msgaddr.go MsgAddr.AddAddresses 100.00% (5/5) +github.com/conformal/btcwire/msggetdata.go MsgGetData.AddInvVect 100.00% (5/5) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.AddInvVect 100.00% (5/5) +github.com/conformal/btcwire/common.go writeElements 100.00% (5/5) +github.com/conformal/btcwire/shahash.go ShaHash.SetBytes 100.00% (5/5) +github.com/conformal/btcwire/msginv.go MsgInv.AddInvVect 100.00% (5/5) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.AddBlockLocatorHash 100.00% (5/5) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.AddBlockLocatorHash 100.00% (5/5) +github.com/conformal/btcwire/netaddress.go NewNetAddress 100.00% (5/5) +github.com/conformal/btcwire/msgping.go MsgPing.MaxPayloadLength 100.00% (4/4) +github.com/conformal/btcwire/msgpong.go MsgPong.MaxPayloadLength 100.00% (4/4) +github.com/conformal/btcwire/msgmempool.go MsgMemPool.BtcEncode 100.00% (4/4) +github.com/conformal/btcwire/shahash.go ShaHash.String 100.00% (4/4) +github.com/conformal/btcwire/netaddress.go maxNetAddressPayload 100.00% (4/4) +github.com/conformal/btcwire/msgmempool.go MsgMemPool.BtcDecode 100.00% (4/4) +github.com/conformal/btcwire/shahash.go ShaHash.Bytes 100.00% (3/3) +github.com/conformal/btcwire/invvect.go InvType.String 100.00% (3/3) +github.com/conformal/btcwire/msgversion.go MsgVersion.HasService 100.00% (3/3) +github.com/conformal/btcwire/msgblock.go MsgBlock.AddTransaction 100.00% (3/3) +github.com/conformal/btcwire/msgaddr.go MsgAddr.MaxPayloadLength 100.00% (3/3) +github.com/conformal/btcwire/netaddress.go NetAddress.HasService 100.00% (3/3) +github.com/conformal/btcwire/msgblock.go MsgBlock.ClearTransactions 100.00% (2/2) +github.com/conformal/btcwire/netaddress.go NetAddress.SetAddress 100.00% (2/2) +github.com/conformal/btcwire/msgverack.go MsgVerAck.BtcEncode 100.00% (1/1) +github.com/conformal/btcwire/msgverack.go MsgVerAck.BtcDecode 100.00% (1/1) +github.com/conformal/btcwire/common.go readElement 100.00% (1/1) +github.com/conformal/btcwire/common.go writeElement 100.00% (1/1) +github.com/conformal/btcwire/common.go RandomUint64 100.00% (1/1) +github.com/conformal/btcwire/invvect.go NewInvVect 100.00% (1/1) +github.com/conformal/btcwire/msgalert.go MsgAlert.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/shahash.go ShaHash.IsEqual 100.00% (1/1) +github.com/conformal/btcwire/msgaddr.go MsgAddr.ClearAddresses 100.00% (1/1) +github.com/conformal/btcwire/msgaddr.go MsgAddr.Command 100.00% (1/1) +github.com/conformal/btcwire/msgaddr.go NewMsgAddr 100.00% (1/1) +github.com/conformal/btcwire/blockheader.go NewBlockHeader 100.00% (1/1) +github.com/conformal/btcwire/msgalert.go MsgAlert.Command 100.00% (1/1) +github.com/conformal/btcwire/msgalert.go NewMsgAlert 100.00% (1/1) +github.com/conformal/btcwire/msgblock.go MsgBlock.Command 100.00% (1/1) +github.com/conformal/btcwire/msgblock.go MsgBlock.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgblock.go MsgBlock.BlockSha 100.00% (1/1) +github.com/conformal/btcwire/msgblock.go NewMsgBlock 100.00% (1/1) +github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.BtcDecode 100.00% (1/1) +github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.BtcEncode 100.00% (1/1) +github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.Command 100.00% (1/1) +github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msggetaddr.go NewMsgGetAddr 100.00% (1/1) +github.com/conformal/btcwire/netaddress.go NetAddress.AddService 100.00% (1/1) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.Command 100.00% (1/1) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msggetblocks.go NewMsgGetBlocks 100.00% (1/1) +github.com/conformal/btcwire/msggetdata.go MsgGetData.Command 100.00% (1/1) +github.com/conformal/btcwire/msggetdata.go MsgGetData.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msggetdata.go NewMsgGetData 100.00% (1/1) +github.com/conformal/btcwire/msgverack.go MsgVerAck.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.Command 100.00% (1/1) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgnotfound.go NewMsgNotFound 100.00% (1/1) +github.com/conformal/btcwire/msgping.go MsgPing.Command 100.00% (1/1) +github.com/conformal/btcwire/msgping.go NewMsgPing 100.00% (1/1) +github.com/conformal/btcwire/msgpong.go MsgPong.Command 100.00% (1/1) +github.com/conformal/btcwire/msgpong.go NewMsgPong 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go NewOutPoint 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go NewTxIn 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go NewTxOut 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go MsgTx.AddTxIn 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go MsgTx.AddTxOut 100.00% (1/1) +github.com/conformal/btcwire/msgverack.go MsgVerAck.Command 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go MsgTx.Command 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go MsgTx.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgtx.go NewMsgTx 100.00% (1/1) +github.com/conformal/btcwire/msgversion.go NewMsgVersion 100.00% (1/1) +github.com/conformal/btcwire/msgversion.go MsgVersion.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.Command 100.00% (1/1) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msggetheaders.go NewMsgGetHeaders 100.00% (1/1) +github.com/conformal/btcwire/msgversion.go MsgVersion.Command 100.00% (1/1) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.Command 100.00% (1/1) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgheaders.go NewMsgHeaders 100.00% (1/1) +github.com/conformal/btcwire/msgversion.go MsgVersion.AddService 100.00% (1/1) +github.com/conformal/btcwire/msginv.go MsgInv.Command 100.00% (1/1) +github.com/conformal/btcwire/msginv.go MsgInv.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msginv.go NewMsgInv 100.00% (1/1) +github.com/conformal/btcwire/msgmempool.go MsgMemPool.Command 100.00% (1/1) +github.com/conformal/btcwire/msgmempool.go MsgMemPool.MaxPayloadLength 100.00% (1/1) +github.com/conformal/btcwire/msgmempool.go NewMsgMemPool 100.00% (1/1) +github.com/conformal/btcwire/msgverack.go NewMsgVerAck 100.00% (1/1) +github.com/conformal/btcwire/message.go makeEmptyMessage 94.44% (17/18) +github.com/conformal/btcwire/msgblock.go MsgBlock.TxShas 85.71% (6/7) +github.com/conformal/btcwire/msgpong.go MsgPong.BtcEncode 85.71% (6/7) +github.com/conformal/btcwire/msgpong.go MsgPong.BtcDecode 85.71% (6/7) +github.com/conformal/btcwire/msgtx.go MsgTx.TxSha 85.71% (6/7) +github.com/conformal/btcwire/netaddress.go readNetAddress 85.00% (17/20) +github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecode 80.00% (8/10) +github.com/conformal/btcwire/blockheader.go readBlockHeader 80.00% (8/10) +github.com/conformal/btcwire/shahash.go NewShaHash 80.00% (4/5) +github.com/conformal/btcwire/msgping.go MsgPing.BtcDecode 80.00% (4/5) +github.com/conformal/btcwire/msgping.go MsgPing.BtcEncode 80.00% (4/5) +github.com/conformal/btcwire/common.go readElements 80.00% (4/5) +github.com/conformal/btcwire/netaddress.go writeNetAddress 78.57% (11/14) +github.com/conformal/btcwire/msgblock.go MsgBlock.BtcEncode 77.78% (7/9) +github.com/conformal/btcwire/msgtx.go readTxIn 76.47% (13/17) +github.com/conformal/btcwire/msgtx.go MsgTx.BtcDecode 76.00% (19/25) +github.com/conformal/btcwire/msgtx.go readTxOut 75.00% (9/12) +github.com/conformal/btcwire/blockheader.go BlockHeader.BlockSha 75.00% (6/8) +github.com/conformal/btcwire/blockheader.go writeBlockHeader 75.00% (6/8) +github.com/conformal/btcwire/msgalert.go MsgAlert.BtcDecode 75.00% (6/8) +github.com/conformal/btcwire/msgalert.go MsgAlert.BtcEncode 75.00% (6/8) +github.com/conformal/btcwire/invvect.go readInvVect 75.00% (3/4) +github.com/conformal/btcwire/msgtx.go writeOutPoint 75.00% (3/4) +github.com/conformal/btcwire/msgtx.go readOutPoint 75.00% (3/4) +github.com/conformal/btcwire/invvect.go writeInvVect 75.00% (3/4) +github.com/conformal/btcwire/msgtx.go MsgTx.BtcEncode 73.91% (17/23) +github.com/conformal/btcwire/msgtx.go writeTxIn 73.33% (11/15) +github.com/conformal/btcwire/msgtx.go writeTxOut 72.73% (8/11) +github.com/conformal/btcwire/message.go WriteMessage 70.00% (21/30) +github.com/conformal/btcwire/message.go readMessageHeader 70.00% (7/10) +github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcDecode 69.23% (9/13) +github.com/conformal/btcwire/msginv.go MsgInv.BtcDecode 69.23% (9/13) +github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcDecode 69.23% (9/13) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcDecode 69.23% (9/13) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcDecode 68.42% (13/19) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcDecode 68.42% (13/19) +github.com/conformal/btcwire/msgversion.go MsgVersion.BtcDecode 68.00% (17/25) +github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcEncode 66.67% (12/18) +github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcEncode 66.67% (12/18) +github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcEncode 66.67% (8/12) +github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcEncode 66.67% (8/12) +github.com/conformal/btcwire/msginv.go MsgInv.BtcEncode 66.67% (8/12) +github.com/conformal/btcwire/msgversion.go MsgVersion.BtcEncode 63.64% (14/22) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcDecode 62.50% (10/16) +github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcEncode 60.00% (9/15) +github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcEncode 60.00% (9/15) +github.com/conformal/btcwire/message.go ReadMessage 57.14% (4/7) +github.com/conformal/btcwire/message.go readMessage 53.12% (17/32) +github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecodeTxLoc 0.00% (0/16) +github.com/conformal/btcwire/message.go discardInput 0.00% (0/10) +github.com/conformal/btcwire --------------------------------- 78.43% (731/932) +