Merge btcwire repo into wire directory.
This commit is contained in:
commit
cf6fc57f27
64 changed files with 15191 additions and 0 deletions
125
wire/README.md
Normal file
125
wire/README.md
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
wire
|
||||||
|
====
|
||||||
|
|
||||||
|
[![Build Status](http://img.shields.io/travis/btcsuite/btcd.svg)]
|
||||||
|
(https://travis-ci.org/btcsuite/btcd) [![ISC License]
|
||||||
|
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
||||||
|
|
||||||
|
Package wire implements the bitcoin wire protocol. A comprehensive suite of
|
||||||
|
tests with 100% test coverage is provided to ensure proper functionality.
|
||||||
|
|
||||||
|
There is an associated blog post about the release of this package
|
||||||
|
[here](https://blog.conformal.com/btcwire-the-bitcoin-wire-protocol-package-from-btcd/).
|
||||||
|
|
||||||
|
This package has intentionally been designed so it can be used as a standalone
|
||||||
|
package for any projects needing to interface with bitcoin peers at the wire
|
||||||
|
protocol level.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
|
||||||
|
(http://godoc.org/github.com/btcsuite/btcd/wire)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://godoc.org/github.com/btcsuite/btcd/wire
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/btcsuite/btcd/wire
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/btcsuite/btcd/wire
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bitcoin Message Overview
|
||||||
|
|
||||||
|
The bitcoin protocol consists of exchanging messages between peers. Each message
|
||||||
|
is preceded by a header which identifies information about it such as which
|
||||||
|
bitcoin network it is a part of, its type, how big it is, and a checksum to
|
||||||
|
verify validity. All encoding and decoding of message headers is handled by this
|
||||||
|
package.
|
||||||
|
|
||||||
|
To accomplish this, there is a generic interface for bitcoin messages named
|
||||||
|
`Message` which allows messages of any type to be read, written, or passed
|
||||||
|
around through channels, functions, etc. In addition, concrete implementations
|
||||||
|
of most of the currently supported bitcoin messages are provided. For these
|
||||||
|
supported messages, all of the details of marshalling and unmarshalling to and
|
||||||
|
from the wire using bitcoin encoding are handled so the caller doesn't have to
|
||||||
|
concern themselves with the specifics.
|
||||||
|
|
||||||
|
## Reading Messages Example
|
||||||
|
|
||||||
|
In order to unmarshal bitcoin messages from the wire, use the `ReadMessage`
|
||||||
|
function. It accepts any `io.Reader`, but typically this will be a `net.Conn`
|
||||||
|
to a remote node running a bitcoin peer. Example syntax is:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Use the most recent protocol version supported by the package and the
|
||||||
|
// main bitcoin network.
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
btcnet := wire.MainNet
|
||||||
|
|
||||||
|
// Reads and validates the next bitcoin message from conn using the
|
||||||
|
// protocol version pver and the bitcoin network btcnet. The returns
|
||||||
|
// are a wire.Message, a []byte which contains the unmarshalled
|
||||||
|
// raw payload, and a possible error.
|
||||||
|
msg, rawPayload, err := wire.ReadMessage(conn, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the package documentation for details on determining the message type.
|
||||||
|
|
||||||
|
## Writing Messages Example
|
||||||
|
|
||||||
|
In order to marshal bitcoin messages to the wire, use the `WriteMessage`
|
||||||
|
function. It accepts any `io.Writer`, but typically this will be a `net.Conn`
|
||||||
|
to a remote node running a bitcoin peer. Example syntax to request addresses
|
||||||
|
from a remote peer is:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Use the most recent protocol version supported by the package and the
|
||||||
|
// main bitcoin network.
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
btcnet := wire.MainNet
|
||||||
|
|
||||||
|
// Create a new getaddr bitcoin message.
|
||||||
|
msg := wire.NewMsgGetAddr()
|
||||||
|
|
||||||
|
// Writes a bitcoin message msg to conn using the protocol version
|
||||||
|
// pver, and the bitcoin network btcnet. The return is a possible
|
||||||
|
// error.
|
||||||
|
err := wire.WriteMessage(conn, msg, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Package wire is licensed under the [copyfree](http://copyfree.org) ISC
|
||||||
|
License.
|
394
wire/bench_test.go
Normal file
394
wire/bench_test.go
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for
|
||||||
|
// the main network, regression test network, and test network (version 3).
|
||||||
|
var genesisCoinbaseTx = MsgTx{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: OutPoint{
|
||||||
|
Hash: ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */
|
||||||
|
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */
|
||||||
|
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */
|
||||||
|
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */
|
||||||
|
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */
|
||||||
|
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */
|
||||||
|
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/
|
||||||
|
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */
|
||||||
|
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/
|
||||||
|
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*TxOut{
|
||||||
|
{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */
|
||||||
|
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */
|
||||||
|
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */
|
||||||
|
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */
|
||||||
|
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */
|
||||||
|
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */
|
||||||
|
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */
|
||||||
|
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */
|
||||||
|
0x1d, 0x5f, 0xac, /* |._.| */
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockOne is the first block in the mainnet block chain.
|
||||||
|
var blockOne = MsgBlock{
|
||||||
|
Header: BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: ShaHash([HashSize]byte{ // Make go vet happy.
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}),
|
||||||
|
MerkleRoot: ShaHash([HashSize]byte{ // Make go vet happy.
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST
|
||||||
|
Bits: 0x1d00ffff, // 486604799
|
||||||
|
Nonce: 0x9962e301, // 2573394689
|
||||||
|
},
|
||||||
|
Transactions: []*MsgTx{
|
||||||
|
{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: OutPoint{
|
||||||
|
Hash: ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04,
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*TxOut{
|
||||||
|
{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarInt1 performs a benchmark on how long it takes to write
|
||||||
|
// a single byte variable length integer.
|
||||||
|
func BenchmarkWriteVarInt1(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarInt(ioutil.Discard, 0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarInt3 performs a benchmark on how long it takes to write
|
||||||
|
// a three byte variable length integer.
|
||||||
|
func BenchmarkWriteVarInt3(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarInt(ioutil.Discard, 0, 65535)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarInt5 performs a benchmark on how long it takes to write
|
||||||
|
// a five byte variable length integer.
|
||||||
|
func BenchmarkWriteVarInt5(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarInt(ioutil.Discard, 0, 4294967295)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarInt9 performs a benchmark on how long it takes to write
|
||||||
|
// a nine byte variable length integer.
|
||||||
|
func BenchmarkWriteVarInt9(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarInt(ioutil.Discard, 0, 18446744073709551615)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarInt1 performs a benchmark on how long it takes to read
|
||||||
|
// a single byte variable length integer.
|
||||||
|
func BenchmarkReadVarInt1(b *testing.B) {
|
||||||
|
buf := []byte{0x01}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarInt(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarInt3 performs a benchmark on how long it takes to read
|
||||||
|
// a three byte variable length integer.
|
||||||
|
func BenchmarkReadVarInt3(b *testing.B) {
|
||||||
|
buf := []byte{0x0fd, 0xff, 0xff}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarInt(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarInt5 performs a benchmark on how long it takes to read
|
||||||
|
// a five byte variable length integer.
|
||||||
|
func BenchmarkReadVarInt5(b *testing.B) {
|
||||||
|
buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarInt(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarInt9 performs a benchmark on how long it takes to read
|
||||||
|
// a nine byte variable length integer.
|
||||||
|
func BenchmarkReadVarInt9(b *testing.B) {
|
||||||
|
buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarInt(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarStr4 performs a benchmark on how long it takes to read a
|
||||||
|
// four byte variable length string.
|
||||||
|
func BenchmarkReadVarStr4(b *testing.B) {
|
||||||
|
buf := []byte{0x04, 't', 'e', 's', 't'}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarString(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadVarStr10 performs a benchmark on how long it takes to read a
|
||||||
|
// ten byte variable length string.
|
||||||
|
func BenchmarkReadVarStr10(b *testing.B) {
|
||||||
|
buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readVarString(bytes.NewReader(buf), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarStr4 performs a benchmark on how long it takes to write a
|
||||||
|
// four byte variable length string.
|
||||||
|
func BenchmarkWriteVarStr4(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarString(ioutil.Discard, 0, "test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteVarStr10 performs a benchmark on how long it takes to write a
|
||||||
|
// ten byte variable length string.
|
||||||
|
func BenchmarkWriteVarStr10(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeVarString(ioutil.Discard, 0, "test012345")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadOutPoint performs a benchmark on how long it takes to read a
|
||||||
|
// transaction output point.
|
||||||
|
func BenchmarkReadOutPoint(b *testing.B) {
|
||||||
|
buf := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||||
|
}
|
||||||
|
var op OutPoint
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readOutPoint(bytes.NewReader(buf), 0, 0, &op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteOutPoint performs a benchmark on how long it takes to write a
|
||||||
|
// transaction output point.
|
||||||
|
func BenchmarkWriteOutPoint(b *testing.B) {
|
||||||
|
op := &OutPoint{
|
||||||
|
Hash: ShaHash{},
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeOutPoint(ioutil.Discard, 0, 0, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadTxOut performs a benchmark on how long it takes to read a
|
||||||
|
// transaction output.
|
||||||
|
func BenchmarkReadTxOut(b *testing.B) {
|
||||||
|
buf := []byte{
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
}
|
||||||
|
var txOut TxOut
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readTxOut(bytes.NewReader(buf), 0, 0, &txOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteTxOut performs a benchmark on how long it takes to write
|
||||||
|
// a transaction output.
|
||||||
|
func BenchmarkWriteTxOut(b *testing.B) {
|
||||||
|
txOut := blockOne.Transactions[0].TxOut[0]
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeTxOut(ioutil.Discard, 0, 0, txOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadTxIn performs a benchmark on how long it takes to read a
|
||||||
|
// transaction input.
|
||||||
|
func BenchmarkReadTxIn(b *testing.B) {
|
||||||
|
buf := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
}
|
||||||
|
var txIn TxIn
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readTxIn(bytes.NewReader(buf), 0, 0, &txIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteTxIn performs a benchmark on how long it takes to write
|
||||||
|
// a transaction input.
|
||||||
|
func BenchmarkWriteTxIn(b *testing.B) {
|
||||||
|
txIn := blockOne.Transactions[0].TxIn[0]
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeTxIn(ioutil.Discard, 0, 0, txIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkDeserializeTx performs a benchmark on how long it takes to
|
||||||
|
// deserialize a transaction.
|
||||||
|
func BenchmarkDeserializeTx(b *testing.B) {
|
||||||
|
buf := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of output transactions
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
var tx MsgTx
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tx.Deserialize(bytes.NewReader(buf))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkSerializeTx performs a benchmark on how long it takes to serialize
|
||||||
|
// a transaction.
|
||||||
|
func BenchmarkSerializeTx(b *testing.B) {
|
||||||
|
tx := blockOne.Transactions[0]
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tx.Serialize(ioutil.Discard)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadBlockHeader performs a benchmark on how long it takes to
|
||||||
|
// deserialize a block header.
|
||||||
|
func BenchmarkReadBlockHeader(b *testing.B) {
|
||||||
|
buf := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, // Nonce
|
||||||
|
0x00, // TxnCount Varint
|
||||||
|
}
|
||||||
|
var header BlockHeader
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
readBlockHeader(bytes.NewReader(buf), 0, &header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkWriteBlockHeader performs a benchmark on how long it takes to
|
||||||
|
// serialize a block header.
|
||||||
|
func BenchmarkWriteBlockHeader(b *testing.B) {
|
||||||
|
header := blockOne.Header
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
writeBlockHeader(ioutil.Discard, 0, &header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkTxSha performs a benchmark on how long it takes to hash a
|
||||||
|
// transaction.
|
||||||
|
func BenchmarkTxSha(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
genesisCoinbaseTx.TxSha()
|
||||||
|
}
|
||||||
|
}
|
131
wire/blockheader.go
Normal file
131
wire/blockheader.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockVersion is the current latest supported block version.
|
||||||
|
const BlockVersion = 2
|
||||||
|
|
||||||
|
// Version 4 bytes + Timestamp 4 bytes + Bits 4 bytes + Nonce 4 bytes +
|
||||||
|
// PrevBlock and MerkleRoot hashes.
|
||||||
|
const MaxBlockHeaderPayload = 16 + (HashSize * 2)
|
||||||
|
|
||||||
|
// BlockHeader defines information about a block and is used in the bitcoin
|
||||||
|
// block (MsgBlock) and headers (MsgHeaders) messages.
|
||||||
|
type BlockHeader struct {
|
||||||
|
// Version of the block. This is not the same as the protocol version.
|
||||||
|
Version int32
|
||||||
|
|
||||||
|
// Hash of the previous block in the block chain.
|
||||||
|
PrevBlock ShaHash
|
||||||
|
|
||||||
|
// Merkle tree reference to hash of all transactions for the block.
|
||||||
|
MerkleRoot ShaHash
|
||||||
|
|
||||||
|
// Time the block was created. This is, unfortunately, encoded as a
|
||||||
|
// uint32 on the wire and therefore is limited to 2106.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Difficulty target for the block.
|
||||||
|
Bits uint32
|
||||||
|
|
||||||
|
// Nonce used to generate the block.
|
||||||
|
Nonce uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockHeaderLen is a constant that represents the number of bytes for a block
|
||||||
|
// header.
|
||||||
|
const blockHeaderLen = 80
|
||||||
|
|
||||||
|
// BlockSha computes the block identifier hash for the given block header.
|
||||||
|
func (h *BlockHeader) BlockSha() (ShaHash, error) {
|
||||||
|
// Encode the header and run double sha256 everything prior to the
|
||||||
|
// number of transactions. Ignore the error returns since there is no
|
||||||
|
// way the encode could fail except being out of memory which would
|
||||||
|
// cause a run-time panic. Also, SetBytes can't fail here due to the
|
||||||
|
// fact DoubleSha256 always returns a []byte of the right size
|
||||||
|
// regardless of input.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var sha ShaHash
|
||||||
|
_ = writeBlockHeader(&buf, 0, h)
|
||||||
|
_ = sha.SetBytes(DoubleSha256(buf.Bytes()[0:blockHeaderLen]))
|
||||||
|
|
||||||
|
// Even though this function can't currently fail, it still returns
|
||||||
|
// a potential error to help future proof the API should a failure
|
||||||
|
// become possible.
|
||||||
|
return sha, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes a block header from r into the receiver using a format
|
||||||
|
// that is suitable for long-term storage such as a database while respecting
|
||||||
|
// the Version field.
|
||||||
|
func (h *BlockHeader) Deserialize(r io.Reader) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of readBlockHeader.
|
||||||
|
return readBlockHeader(r, 0, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize encodes a block header from r into the receiver using a format
|
||||||
|
// that is suitable for long-term storage such as a database while respecting
|
||||||
|
// the Version field.
|
||||||
|
func (h *BlockHeader) Serialize(w io.Writer) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of writeBlockHeader.
|
||||||
|
return writeBlockHeader(w, 0, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockHeader returns a new BlockHeader using the provided previous block
|
||||||
|
// hash, merkle root hash, difficulty bits, and nonce used to generate the
|
||||||
|
// block with defaults for the remaining fields.
|
||||||
|
func NewBlockHeader(prevHash *ShaHash, merkleRootHash *ShaHash, bits uint32,
|
||||||
|
nonce uint32) *BlockHeader {
|
||||||
|
|
||||||
|
// Limit the timestamp to one second precision since the protocol
|
||||||
|
// doesn't support better.
|
||||||
|
return &BlockHeader{
|
||||||
|
Version: BlockVersion,
|
||||||
|
PrevBlock: *prevHash,
|
||||||
|
MerkleRoot: *merkleRootHash,
|
||||||
|
Timestamp: time.Unix(time.Now().Unix(), 0),
|
||||||
|
Bits: bits,
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBlockHeader reads a bitcoin block header from r. See Deserialize for
|
||||||
|
// decoding block headers stored to disk, such as in a database, as opposed to
|
||||||
|
// decoding from the wire.
|
||||||
|
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||||
|
var sec uint32
|
||||||
|
err := readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot, &sec,
|
||||||
|
&bh.Bits, &bh.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bh.Timestamp = time.Unix(int64(sec), 0)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBlockHeader writes a bitcoin block header to w. See Serialize for
|
||||||
|
// encoding block headers to be stored to disk, such as in a database, as
|
||||||
|
// opposed to encoding for the wire.
|
||||||
|
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||||
|
sec := uint32(bh.Timestamp.Unix())
|
||||||
|
err := writeElements(w, bh.Version, &bh.PrevBlock, &bh.MerkleRoot,
|
||||||
|
sec, bh.Bits, bh.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
231
wire/blockheader_test.go
Normal file
231
wire/blockheader_test.go
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBlockHeader tests the BlockHeader API.
|
||||||
|
func TestBlockHeader(t *testing.T) {
|
||||||
|
nonce64, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
nonce := uint32(nonce64)
|
||||||
|
|
||||||
|
hash := mainNetGenesisHash
|
||||||
|
merkleHash := mainNetGenesisMerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
// Ensure we get the same data back out.
|
||||||
|
if !bh.PrevBlock.IsEqual(&hash) {
|
||||||
|
t.Errorf("NewBlockHeader: wrong prev hash - got %v, want %v",
|
||||||
|
spew.Sprint(bh.PrevBlock), spew.Sprint(hash))
|
||||||
|
}
|
||||||
|
if !bh.MerkleRoot.IsEqual(&merkleHash) {
|
||||||
|
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||||
|
spew.Sprint(bh.MerkleRoot), spew.Sprint(merkleHash))
|
||||||
|
}
|
||||||
|
if bh.Bits != bits {
|
||||||
|
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||||
|
bh.Bits, bits)
|
||||||
|
}
|
||||||
|
if bh.Nonce != nonce {
|
||||||
|
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||||
|
bh.Nonce, nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockHeaderWire tests the BlockHeader wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestBlockHeaderWire(t *testing.T) {
|
||||||
|
nonce := uint32(123123) // 0x1e0f3
|
||||||
|
|
||||||
|
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
baseBlockHdr := &wire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: mainNetGenesisHash,
|
||||||
|
MerkleRoot: mainNetGenesisMerkleRoot,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Bits: bits,
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr.
|
||||||
|
baseBlockHdrEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, // Nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.BlockHeader // Data to encode
|
||||||
|
out *wire.BlockHeader // Expected decoded data
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteBlockHeader(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeBlockHeader #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the block header from wire format.
|
||||||
|
var bh wire.BlockHeader
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = wire.TstReadBlockHeader(rbuf, test.pver, &bh)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readBlockHeader #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&bh, test.out) {
|
||||||
|
t.Errorf("readBlockHeader #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockHeaderSerialize tests BlockHeader serialize and deserialize.
|
||||||
|
func TestBlockHeaderSerialize(t *testing.T) {
|
||||||
|
nonce := uint32(123123) // 0x1e0f3
|
||||||
|
|
||||||
|
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
baseBlockHdr := &wire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: mainNetGenesisHash,
|
||||||
|
MerkleRoot: mainNetGenesisMerkleRoot,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Bits: bits,
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr.
|
||||||
|
baseBlockHdrEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, // Nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.BlockHeader // Data to encode
|
||||||
|
out *wire.BlockHeader // Expected decoded data
|
||||||
|
buf []byte // Serialized data
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Serialize the block header.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.Serialize(&buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Serialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the block header.
|
||||||
|
var bh wire.BlockHeader
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = bh.Deserialize(rbuf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Deserialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&bh, test.out) {
|
||||||
|
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
531
wire/common.go
Normal file
531
wire/common.go
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/btcsuite/fastsha256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum payload size for a variable length integer.
|
||||||
|
const MaxVarIntPayload = 9
|
||||||
|
|
||||||
|
// readElement reads the next sequence of bytes from r using little endian
|
||||||
|
// depending on the concrete type of element pointed to.
|
||||||
|
func readElement(r io.Reader, element interface{}) error {
|
||||||
|
var scratch [8]byte
|
||||||
|
|
||||||
|
// Attempt to read the element based on the concrete type via fast
|
||||||
|
// type assertions first.
|
||||||
|
switch e := element.(type) {
|
||||||
|
case *int32:
|
||||||
|
b := scratch[0:4]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = int32(binary.LittleEndian.Uint32(b))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *uint32:
|
||||||
|
b := scratch[0:4]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = binary.LittleEndian.Uint32(b)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *int64:
|
||||||
|
b := scratch[0:8]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = int64(binary.LittleEndian.Uint64(b))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *uint64:
|
||||||
|
b := scratch[0:8]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = binary.LittleEndian.Uint64(b)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *bool:
|
||||||
|
b := scratch[0:1]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b[0] == 0x00 {
|
||||||
|
*e = false
|
||||||
|
} else {
|
||||||
|
*e = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Message header checksum.
|
||||||
|
case *[4]byte:
|
||||||
|
_, err := io.ReadFull(r, e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Message header command.
|
||||||
|
case *[CommandSize]uint8:
|
||||||
|
_, err := io.ReadFull(r, e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// IP address.
|
||||||
|
case *[16]byte:
|
||||||
|
_, err := io.ReadFull(r, e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *ShaHash:
|
||||||
|
_, err := io.ReadFull(r, e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *ServiceFlag:
|
||||||
|
b := scratch[0:8]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = ServiceFlag(binary.LittleEndian.Uint64(b))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *InvType:
|
||||||
|
b := scratch[0:4]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = InvType(binary.LittleEndian.Uint32(b))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *BitcoinNet:
|
||||||
|
b := scratch[0:4]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = BitcoinNet(binary.LittleEndian.Uint32(b))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *BloomUpdateType:
|
||||||
|
b := scratch[0:1]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = BloomUpdateType(b[0])
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *RejectCode:
|
||||||
|
b := scratch[0:1]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = RejectCode(b[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the slower binary.Read if a fast path was not available
|
||||||
|
// above.
|
||||||
|
return binary.Read(r, binary.LittleEndian, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readElements reads multiple items from r. It is equivalent to multiple
|
||||||
|
// calls to readElement.
|
||||||
|
func readElements(r io.Reader, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
err := readElement(r, element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeElement writes the little endian representation of element to w.
|
||||||
|
func writeElement(w io.Writer, element interface{}) error {
|
||||||
|
var scratch [8]byte
|
||||||
|
|
||||||
|
// Attempt to write the element based on the concrete type via fast
|
||||||
|
// type assertions first.
|
||||||
|
switch e := element.(type) {
|
||||||
|
case int32:
|
||||||
|
b := scratch[0:4]
|
||||||
|
binary.LittleEndian.PutUint32(b, uint32(e))
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case uint32:
|
||||||
|
b := scratch[0:4]
|
||||||
|
binary.LittleEndian.PutUint32(b, e)
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case int64:
|
||||||
|
b := scratch[0:8]
|
||||||
|
binary.LittleEndian.PutUint64(b, uint64(e))
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case uint64:
|
||||||
|
b := scratch[0:8]
|
||||||
|
binary.LittleEndian.PutUint64(b, e)
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case bool:
|
||||||
|
b := scratch[0:1]
|
||||||
|
if e == true {
|
||||||
|
b[0] = 0x01
|
||||||
|
} else {
|
||||||
|
b[0] = 0x00
|
||||||
|
}
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Message header checksum.
|
||||||
|
case [4]byte:
|
||||||
|
_, err := w.Write(e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Message header command.
|
||||||
|
case [CommandSize]uint8:
|
||||||
|
_, err := w.Write(e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// IP address.
|
||||||
|
case [16]byte:
|
||||||
|
_, err := w.Write(e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *ShaHash:
|
||||||
|
_, err := w.Write(e[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ServiceFlag:
|
||||||
|
b := scratch[0:8]
|
||||||
|
binary.LittleEndian.PutUint64(b, uint64(e))
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case InvType:
|
||||||
|
b := scratch[0:4]
|
||||||
|
binary.LittleEndian.PutUint32(b, uint32(e))
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case BitcoinNet:
|
||||||
|
b := scratch[0:4]
|
||||||
|
binary.LittleEndian.PutUint32(b, uint32(e))
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case BloomUpdateType:
|
||||||
|
b := scratch[0:1]
|
||||||
|
b[0] = uint8(e)
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case RejectCode:
|
||||||
|
b := scratch[0:1]
|
||||||
|
b[0] = uint8(e)
|
||||||
|
_, err := w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to the slower binary.Write if a fast path was not available
|
||||||
|
// above.
|
||||||
|
return binary.Write(w, binary.LittleEndian, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeElements writes multiple items to w. It is equivalent to multiple
|
||||||
|
// calls to writeElement.
|
||||||
|
func writeElements(w io.Writer, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
err := writeElement(w, element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readVarInt reads a variable length integer from r and returns it as a uint64.
|
||||||
|
func readVarInt(r io.Reader, pver uint32) (uint64, error) {
|
||||||
|
var b [8]byte
|
||||||
|
_, err := io.ReadFull(r, b[0:1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rv uint64
|
||||||
|
discriminant := uint8(b[0])
|
||||||
|
switch discriminant {
|
||||||
|
case 0xff:
|
||||||
|
_, err := io.ReadFull(r, b[:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = binary.LittleEndian.Uint64(b[:])
|
||||||
|
|
||||||
|
case 0xfe:
|
||||||
|
_, err := io.ReadFull(r, b[0:4])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = uint64(binary.LittleEndian.Uint32(b[:]))
|
||||||
|
|
||||||
|
case 0xfd:
|
||||||
|
_, err := io.ReadFull(r, b[0:2])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = uint64(binary.LittleEndian.Uint16(b[:]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
rv = uint64(discriminant)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVarInt serializes val to w using a variable number of bytes depending
|
||||||
|
// on its value.
|
||||||
|
func writeVarInt(w io.Writer, pver uint32, val uint64) error {
|
||||||
|
if val < 0xfd {
|
||||||
|
_, err := w.Write([]byte{uint8(val)})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val <= math.MaxUint16 {
|
||||||
|
var buf [3]byte
|
||||||
|
buf[0] = 0xfd
|
||||||
|
binary.LittleEndian.PutUint16(buf[1:], uint16(val))
|
||||||
|
_, err := w.Write(buf[:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val <= math.MaxUint32 {
|
||||||
|
var buf [5]byte
|
||||||
|
buf[0] = 0xfe
|
||||||
|
binary.LittleEndian.PutUint32(buf[1:], uint32(val))
|
||||||
|
_, err := w.Write(buf[:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [9]byte
|
||||||
|
buf[0] = 0xff
|
||||||
|
binary.LittleEndian.PutUint64(buf[1:], val)
|
||||||
|
_, err := w.Write(buf[:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarIntSerializeSize returns the number of bytes it would take to serialize
|
||||||
|
// val as a variable length integer.
|
||||||
|
func VarIntSerializeSize(val uint64) int {
|
||||||
|
// The value is small enough to be represented by itself, so it's
|
||||||
|
// just 1 byte.
|
||||||
|
if val < 0xfd {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discriminant 1 byte plus 2 bytes for the uint16.
|
||||||
|
if val <= math.MaxUint16 {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discriminant 1 byte plus 4 bytes for the uint32.
|
||||||
|
if val <= math.MaxUint32 {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discriminant 1 byte plus 8 bytes for the uint64.
|
||||||
|
return 9
|
||||||
|
}
|
||||||
|
|
||||||
|
// readVarString reads a variable length string from r and returns it as a Go
|
||||||
|
// string. A varString is encoded as a varInt containing the length of the
|
||||||
|
// string, and the bytes that represent the string itself. An error is returned
|
||||||
|
// if the length is greater than the maximum block payload size, since it would
|
||||||
|
// not be possible to put a varString of that size into a block anyways and it
|
||||||
|
// also helps protect against memory exhaustion attacks and forced panics
|
||||||
|
// through malformed messages.
|
||||||
|
func readVarString(r io.Reader, pver uint32) (string, error) {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent variable length strings that are larger than the maximum
|
||||||
|
// message size. It would be possible to cause memory exhaustion and
|
||||||
|
// panics without a sane upper bound on this count.
|
||||||
|
if count > MaxMessagePayload {
|
||||||
|
str := fmt.Sprintf("variable length string is too long "+
|
||||||
|
"[count %d, max %d]", count, MaxMessagePayload)
|
||||||
|
return "", messageError("readVarString", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, count)
|
||||||
|
_, err = io.ReadFull(r, buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVarString serializes str to w as a varInt containing the length of the
|
||||||
|
// string followed by the bytes that represent the string itself.
|
||||||
|
func writeVarString(w io.Writer, pver uint32, str string) error {
|
||||||
|
err := writeVarInt(w, pver, uint64(len(str)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readVarBytes reads a variable length byte array. A byte array is encoded
|
||||||
|
// as a varInt containing the length of the array followed by the bytes
|
||||||
|
// themselves. An error is returned if the length is greater than the
|
||||||
|
// passed maxAllowed parameter which helps protect against memory exhuastion
|
||||||
|
// attacks and forced panics thorugh malformed messages. The fieldName
|
||||||
|
// parameter is only used for the error message so it provides more context in
|
||||||
|
// the error.
|
||||||
|
func readVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
|
||||||
|
fieldName string) ([]byte, error) {
|
||||||
|
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent byte array larger than the max message size. It would
|
||||||
|
// be possible to cause memory exhaustion and panics without a sane
|
||||||
|
// upper bound on this count.
|
||||||
|
if count > uint64(maxAllowed) {
|
||||||
|
str := fmt.Sprintf("%s is larger than the max allowed size "+
|
||||||
|
"[count %d, max %d]", fieldName, count, maxAllowed)
|
||||||
|
return nil, messageError("readVarBytes", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, count)
|
||||||
|
_, err = io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVarInt serializes a variable length byte array to w as a varInt
|
||||||
|
// containing the number of bytes, followed by the bytes themselves.
|
||||||
|
func writeVarBytes(w io.Writer, pver uint32, bytes []byte) error {
|
||||||
|
slen := uint64(len(bytes))
|
||||||
|
err := writeVarInt(w, pver, slen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomUint64 returns a cryptographically random uint64 value. This
|
||||||
|
// unexported version takes a reader primarily to ensure the error paths
|
||||||
|
// can be properly tested by passing a fake reader in the tests.
|
||||||
|
func randomUint64(r io.Reader) (uint64, error) {
|
||||||
|
var b [8]byte
|
||||||
|
_, err := io.ReadFull(r, b[:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint64(b[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomUint64 returns a cryptographically random uint64 value.
|
||||||
|
func RandomUint64() (uint64, error) {
|
||||||
|
return randomUint64(rand.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes.
|
||||||
|
func DoubleSha256(b []byte) []byte {
|
||||||
|
hasher := fastsha256.New()
|
||||||
|
hasher.Write(b)
|
||||||
|
sum := hasher.Sum(nil)
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(sum)
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
703
wire/common_test.go
Normal file
703
wire/common_test.go
Normal file
|
@ -0,0 +1,703 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mainNetGenesisHash is the hash of the first block in the block chain for the
|
||||||
|
// main network (genesis block).
|
||||||
|
var mainNetGenesisHash = wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
})
|
||||||
|
|
||||||
|
// mainNetGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||||
|
// block for the main network.
|
||||||
|
var mainNetGenesisMerkleRoot = wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
||||||
|
})
|
||||||
|
|
||||||
|
// fakeRandReader implements the io.Reader interface and is used to force
|
||||||
|
// errors in the RandomUint64 function.
|
||||||
|
type fakeRandReader struct {
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read returns the fake reader error and the lesser of the fake reader value
|
||||||
|
// and the length of p.
|
||||||
|
func (r *fakeRandReader) Read(p []byte) (int, error) {
|
||||||
|
n := r.n
|
||||||
|
if n > len(p) {
|
||||||
|
n = len(p)
|
||||||
|
}
|
||||||
|
return n, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestElementWire tests wire encode and decode for various element types. This
|
||||||
|
// is mainly to test the "fast" paths in readElement and writeElement which use
|
||||||
|
// type assertions to avoid reflection when possible.
|
||||||
|
func TestElementWire(t *testing.T) {
|
||||||
|
type writeElementReflect int32
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in interface{} // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
}{
|
||||||
|
{int32(1), []byte{0x01, 0x00, 0x00, 0x00}},
|
||||||
|
{uint32(256), []byte{0x00, 0x01, 0x00, 0x00}},
|
||||||
|
{
|
||||||
|
int64(65536),
|
||||||
|
[]byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uint64(4294967296),
|
||||||
|
[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
true,
|
||||||
|
[]byte{0x01},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
[]byte{0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[4]byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[wire.CommandSize]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[16]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
(*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
}),
|
||||||
|
[]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wire.ServiceFlag(wire.SFNodeNetwork),
|
||||||
|
[]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wire.InvType(wire.InvTypeTx),
|
||||||
|
[]byte{0x01, 0x00, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wire.BitcoinNet(wire.MainNet),
|
||||||
|
[]byte{0xf9, 0xbe, 0xb4, 0xd9},
|
||||||
|
},
|
||||||
|
// Type not supported by the "fast" path and requires reflection.
|
||||||
|
{
|
||||||
|
writeElementReflect(1),
|
||||||
|
[]byte{0x01, 0x00, 0x00, 0x00},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Write to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteElement(&buf, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeElement #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeElement #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
val := test.in
|
||||||
|
if reflect.ValueOf(test.in).Kind() != reflect.Ptr {
|
||||||
|
val = reflect.New(reflect.TypeOf(test.in)).Interface()
|
||||||
|
}
|
||||||
|
err = wire.TstReadElement(rbuf, val)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readElement #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ival := val
|
||||||
|
if reflect.ValueOf(test.in).Kind() != reflect.Ptr {
|
||||||
|
ival = reflect.Indirect(reflect.ValueOf(val)).Interface()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ival, test.in) {
|
||||||
|
t.Errorf("readElement #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(ival), spew.Sdump(test.in))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestElementWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of various element types to confirm error paths work correctly.
|
||||||
|
func TestElementWireErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in interface{} // Value to encode
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
{int32(1), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{uint32(256), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{int64(65536), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{true, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{[4]byte{0x01, 0x02, 0x03, 0x04}, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{
|
||||||
|
[wire.CommandSize]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c,
|
||||||
|
},
|
||||||
|
0, io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[16]byte{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
},
|
||||||
|
0, io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
(*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
}),
|
||||||
|
0, io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
{wire.ServiceFlag(wire.SFNodeNetwork), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{wire.InvType(wire.InvTypeTx), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
{wire.BitcoinNet(wire.MainNet), 0, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := wire.TstWriteElement(w, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeElement #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, nil)
|
||||||
|
val := test.in
|
||||||
|
if reflect.ValueOf(test.in).Kind() != reflect.Ptr {
|
||||||
|
val = reflect.New(reflect.TypeOf(test.in)).Interface()
|
||||||
|
}
|
||||||
|
err = wire.TstReadElement(r, val)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readElement #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarIntWire tests wire encode and decode for variable length integers.
|
||||||
|
func TestVarIntWire(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in uint64 // Value to encode
|
||||||
|
out uint64 // Expected decoded value
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
// Single byte
|
||||||
|
{0, 0, []byte{0x00}, pver},
|
||||||
|
// Max single byte
|
||||||
|
{0xfc, 0xfc, []byte{0xfc}, pver},
|
||||||
|
// Min 2-byte
|
||||||
|
{0xfd, 0xfd, []byte{0xfd, 0x0fd, 0x00}, pver},
|
||||||
|
// Max 2-byte
|
||||||
|
{0xffff, 0xffff, []byte{0xfd, 0xff, 0xff}, pver},
|
||||||
|
// Min 4-byte
|
||||||
|
{0x10000, 0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}, pver},
|
||||||
|
// Max 4-byte
|
||||||
|
{0xffffffff, 0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, pver},
|
||||||
|
// Min 8-byte
|
||||||
|
{
|
||||||
|
0x100000000, 0x100000000,
|
||||||
|
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||||
|
pver,
|
||||||
|
},
|
||||||
|
// Max 8-byte
|
||||||
|
{
|
||||||
|
0xffffffffffffffff, 0xffffffffffffffff,
|
||||||
|
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
pver,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteVarInt(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarInt #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeVarInt #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
val, err := wire.TstReadVarInt(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readVarInt #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val != test.out {
|
||||||
|
t.Errorf("readVarInt #%d\n got: %d want: %d", i,
|
||||||
|
val, test.out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarIntWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of variable length integers to confirm error paths work correctly.
|
||||||
|
func TestVarIntWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in uint64 // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force errors on discriminant.
|
||||||
|
{0, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on 2-byte read/write.
|
||||||
|
{0xfd, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 4-byte read/write.
|
||||||
|
{0x10000, []byte{0xfe}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 8-byte read/write.
|
||||||
|
{0x100000000, []byte{0xff}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := wire.TstWriteVarInt(w, test.pver, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeVarInt #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
_, err = wire.TstReadVarInt(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readVarInt #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarIntWire tests the serialize size for variable length integers.
|
||||||
|
func TestVarIntSerializeSize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
val uint64 // Value to get the serialized size for
|
||||||
|
size int // Expected serialized size
|
||||||
|
}{
|
||||||
|
// Single byte
|
||||||
|
{0, 1},
|
||||||
|
// Max single byte
|
||||||
|
{0xfc, 1},
|
||||||
|
// Min 2-byte
|
||||||
|
{0xfd, 3},
|
||||||
|
// Max 2-byte
|
||||||
|
{0xffff, 3},
|
||||||
|
// Min 4-byte
|
||||||
|
{0x10000, 5},
|
||||||
|
// Max 4-byte
|
||||||
|
{0xffffffff, 5},
|
||||||
|
// Min 8-byte
|
||||||
|
{0x100000000, 9},
|
||||||
|
// Max 8-byte
|
||||||
|
{0xffffffffffffffff, 9},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
serializedSize := wire.VarIntSerializeSize(test.val)
|
||||||
|
if serializedSize != test.size {
|
||||||
|
t.Errorf("VarIntSerializeSize #%d got: %d, want: %d", i,
|
||||||
|
serializedSize, test.size)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarStringWire tests wire encode and decode for variable length strings.
|
||||||
|
func TestVarStringWire(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// str256 is a string that takes a 2-byte varint to encode.
|
||||||
|
str256 := strings.Repeat("test", 64)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in string // String to encode
|
||||||
|
out string // String to decoded value
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
// Empty string
|
||||||
|
{"", "", []byte{0x00}, pver},
|
||||||
|
// Single byte varint + string
|
||||||
|
{"Test", "Test", append([]byte{0x04}, []byte("Test")...), pver},
|
||||||
|
// 2-byte varint + string
|
||||||
|
{str256, str256, append([]byte{0xfd, 0x00, 0x01}, []byte(str256)...), pver},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteVarString(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarString #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeVarString #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
val, err := wire.TstReadVarString(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readVarString #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val != test.out {
|
||||||
|
t.Errorf("readVarString #%d\n got: %s want: %s", i,
|
||||||
|
val, test.out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarStringWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of variable length strings to confirm error paths work correctly.
|
||||||
|
func TestVarStringWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// str256 is a string that takes a 2-byte varint to encode.
|
||||||
|
str256 := strings.Repeat("test", 64)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in string // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force errors on empty string.
|
||||||
|
{"", []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error on single byte varint + string.
|
||||||
|
{"Test", []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 2-byte varint + string.
|
||||||
|
{str256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := wire.TstWriteVarString(w, test.pver, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeVarString #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
_, err = wire.TstReadVarString(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readVarString #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarStringOverflowErrors performs tests to ensure deserializing variable
|
||||||
|
// length strings intentionally crafted to use large values for the string
|
||||||
|
// length are handled properly. This could otherwise potentially be used as an
|
||||||
|
// attack vector.
|
||||||
|
func TestVarStringOverflowErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
err error // Expected error
|
||||||
|
}{
|
||||||
|
{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
pver, &wire.MessageError{}},
|
||||||
|
{[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
||||||
|
pver, &wire.MessageError{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
_, err := wire.TstReadVarString(rbuf, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("readVarString #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarBytesWire tests wire encode and decode for variable length byte array.
|
||||||
|
func TestVarBytesWire(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// bytes256 is a byte array that takes a 2-byte varint to encode.
|
||||||
|
bytes256 := bytes.Repeat([]byte{0x01}, 256)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in []byte // Byte Array to write
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
// Empty byte array
|
||||||
|
{[]byte{}, []byte{0x00}, pver},
|
||||||
|
// Single byte varint + byte array
|
||||||
|
{[]byte{0x01}, []byte{0x01, 0x01}, pver},
|
||||||
|
// 2-byte varint + byte array
|
||||||
|
{bytes256, append([]byte{0xfd, 0x00, 0x01}, bytes256...), pver},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteVarBytes(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarBytes #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeVarBytes #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
val, err := wire.TstReadVarBytes(rbuf, test.pver,
|
||||||
|
wire.MaxMessagePayload, "test payload")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readVarBytes #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("readVarBytes #%d\n got: %s want: %s", i,
|
||||||
|
val, test.buf)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarBytesWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of variable length byte arrays to confirm error paths work correctly.
|
||||||
|
func TestVarBytesWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// bytes256 is a byte array that takes a 2-byte varint to encode.
|
||||||
|
bytes256 := bytes.Repeat([]byte{0x01}, 256)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in []byte // Byte Array to write
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force errors on empty byte array.
|
||||||
|
{[]byte{}, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error on single byte varint + byte array.
|
||||||
|
{[]byte{0x01, 0x02, 0x03}, []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 2-byte varint + byte array.
|
||||||
|
{bytes256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := wire.TstWriteVarBytes(w, test.pver, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeVarBytes #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
_, err = wire.TstReadVarBytes(r, test.pver,
|
||||||
|
wire.MaxMessagePayload, "test payload")
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readVarBytes #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarBytesOverflowErrors performs tests to ensure deserializing variable
|
||||||
|
// length byte arrays intentionally crafted to use large values for the array
|
||||||
|
// length are handled properly. This could otherwise potentially be used as an
|
||||||
|
// attack vector.
|
||||||
|
func TestVarBytesOverflowErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
err error // Expected error
|
||||||
|
}{
|
||||||
|
{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
pver, &wire.MessageError{}},
|
||||||
|
{[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
||||||
|
pver, &wire.MessageError{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
_, err := wire.TstReadVarBytes(rbuf, test.pver,
|
||||||
|
wire.MaxMessagePayload, "test payload")
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("readVarBytes #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRandomUint64 exercises the randomness of the random number generator on
|
||||||
|
// the system by ensuring the probability of the generated numbers. If the RNG
|
||||||
|
// is evenly distributed as a proper cryptographic RNG should be, there really
|
||||||
|
// should only be 1 number < 2^56 in 2^8 tries for a 64-bit number. However,
|
||||||
|
// use a higher number of 5 to really ensure the test doesn't fail unless the
|
||||||
|
// RNG is just horrendous.
|
||||||
|
func TestRandomUint64(t *testing.T) {
|
||||||
|
tries := 1 << 8 // 2^8
|
||||||
|
watermark := uint64(1 << 56) // 2^56
|
||||||
|
maxHits := 5
|
||||||
|
badRNG := "The random number generator on this system is clearly " +
|
||||||
|
"terrible since we got %d values less than %d in %d runs " +
|
||||||
|
"when only %d was expected"
|
||||||
|
|
||||||
|
numHits := 0
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64 iteration %d failed - err %v",
|
||||||
|
i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nonce < watermark {
|
||||||
|
numHits++
|
||||||
|
}
|
||||||
|
if numHits > maxHits {
|
||||||
|
str := fmt.Sprintf(badRNG, numHits, watermark, tries, maxHits)
|
||||||
|
t.Errorf("Random Uint64 iteration %d failed - %v %v", i,
|
||||||
|
str, numHits)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRandomUint64Errors uses a fake reader to force error paths to be executed
|
||||||
|
// and checks the results accordingly.
|
||||||
|
func TestRandomUint64Errors(t *testing.T) {
|
||||||
|
// Test short reads.
|
||||||
|
fr := &fakeRandReader{n: 2, err: io.EOF}
|
||||||
|
nonce, err := wire.TstRandomUint64(fr)
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Errorf("TestRandomUint64Fails: Error not expected value of %v [%v]",
|
||||||
|
io.ErrShortBuffer, err)
|
||||||
|
}
|
||||||
|
if nonce != 0 {
|
||||||
|
t.Errorf("TestRandomUint64Fails: nonce is not 0 [%v]", nonce)
|
||||||
|
}
|
||||||
|
}
|
159
wire/doc.go
Normal file
159
wire/doc.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package wire implements the bitcoin wire protocol.
|
||||||
|
|
||||||
|
For the complete details of the bitcoin protocol, see the official wiki entry
|
||||||
|
at https://en.bitcoin.it/wiki/Protocol_specification. The following only serves
|
||||||
|
as a quick overview to provide information on how to use the package.
|
||||||
|
|
||||||
|
At a high level, this package provides support for marshalling and unmarshalling
|
||||||
|
supported bitcoin messages to and from the wire. This package does not deal
|
||||||
|
with the specifics of message handling such as what to do when a message is
|
||||||
|
received. This provides the caller with a high level of flexibility.
|
||||||
|
|
||||||
|
Bitcoin Message Overview
|
||||||
|
|
||||||
|
The bitcoin protocol consists of exchanging messages between peers. Each
|
||||||
|
message is preceded by a header which identifies information about it such as
|
||||||
|
which bitcoin network it is a part of, its type, how big it is, and a checksum
|
||||||
|
to verify validity. All encoding and decoding of message headers is handled by
|
||||||
|
this package.
|
||||||
|
|
||||||
|
To accomplish this, there is a generic interface for bitcoin messages named
|
||||||
|
Message which allows messages of any type to be read, written, or passed around
|
||||||
|
through channels, functions, etc. In addition, concrete implementations of most
|
||||||
|
of the currently supported bitcoin messages are provided. For these supported
|
||||||
|
messages, all of the details of marshalling and unmarshalling to and from the
|
||||||
|
wire using bitcoin encoding are handled so the caller doesn't have to concern
|
||||||
|
themselves with the specifics.
|
||||||
|
|
||||||
|
Message Interaction
|
||||||
|
|
||||||
|
The following provides a quick summary of how the bitcoin messages are intended
|
||||||
|
to interact with one another. As stated above, these interactions are not
|
||||||
|
directly handled by this package. For more in-depth details about the
|
||||||
|
appropriate interactions, see the official bitcoin protocol wiki entry at
|
||||||
|
https://en.bitcoin.it/wiki/Protocol_specification.
|
||||||
|
|
||||||
|
The initial handshake consists of two peers sending each other a version message
|
||||||
|
(MsgVersion) followed by responding with a verack message (MsgVerAck). Both
|
||||||
|
peers use the information in the version message (MsgVersion) to negotiate
|
||||||
|
things such as protocol version and supported services with each other. Once
|
||||||
|
the initial handshake is complete, the following chart indicates message
|
||||||
|
interactions in no particular order.
|
||||||
|
|
||||||
|
Peer A Sends Peer B Responds
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
getaddr message (MsgGetAddr) addr message (MsgAddr)
|
||||||
|
getblocks message (MsgGetBlocks) inv message (MsgInv)
|
||||||
|
inv message (MsgInv) getdata message (MsgGetData)
|
||||||
|
getdata message (MsgGetData) block message (MsgBlock) -or-
|
||||||
|
tx message (MsgTx) -or-
|
||||||
|
notfound message (MsgNotFound)
|
||||||
|
getheaders message (MsgGetHeaders) headers message (MsgHeaders)
|
||||||
|
ping message (MsgPing) pong message (MsgHeaders)* -or-
|
||||||
|
(none -- Ability to send message is enough)
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
* The pong message was not added until later protocol versions as defined
|
||||||
|
in BIP0031. The BIP0031Version constant can be used to detect a recent
|
||||||
|
enough protocol version for this purpose (version > BIP0031Version).
|
||||||
|
|
||||||
|
Common Parameters
|
||||||
|
|
||||||
|
There are several common parameters that arise when using this package to read
|
||||||
|
and write bitcoin messages. The following sections provide a quick overview of
|
||||||
|
these parameters so the next sections can build on them.
|
||||||
|
|
||||||
|
Protocol Version
|
||||||
|
|
||||||
|
The protocol version should be negotiated with the remote peer at a higher
|
||||||
|
level than this package via the version (MsgVersion) message exchange, however,
|
||||||
|
this package provides the wire.ProtocolVersion constant which indicates the
|
||||||
|
latest protocol version this package supports and is typically the value to use
|
||||||
|
for all outbound connections before a potentially lower protocol version is
|
||||||
|
negotiated.
|
||||||
|
|
||||||
|
Bitcoin Network
|
||||||
|
|
||||||
|
The bitcoin network is a magic number which is used to identify the start of a
|
||||||
|
message and which bitcoin network the message applies to. This package provides
|
||||||
|
the following constants:
|
||||||
|
|
||||||
|
wire.MainNet
|
||||||
|
wire.TestNet (Regression test network)
|
||||||
|
wire.TestNet3 (Test network version 3)
|
||||||
|
wire.SimNet (Simulation test network)
|
||||||
|
|
||||||
|
Determining Message Type
|
||||||
|
|
||||||
|
As discussed in the bitcoin message overview section, this package reads
|
||||||
|
and writes bitcoin messages using a generic interface named Message. In
|
||||||
|
order to determine the actual concrete type of the message, use a type
|
||||||
|
switch or type assertion. An example of a type switch follows:
|
||||||
|
|
||||||
|
// Assumes msg is already a valid concrete message such as one created
|
||||||
|
// via NewMsgVersion or read via ReadMessage.
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *wire.MsgVersion:
|
||||||
|
// The message is a pointer to a MsgVersion struct.
|
||||||
|
fmt.Printf("Protocol version: %v", msg.ProtocolVersion)
|
||||||
|
case *wire.MsgBlock:
|
||||||
|
// The message is a pointer to a MsgBlock struct.
|
||||||
|
fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
Reading Messages
|
||||||
|
|
||||||
|
In order to unmarshall bitcoin messages from the wire, use the ReadMessage
|
||||||
|
function. It accepts any io.Reader, but typically this will be a net.Conn to
|
||||||
|
a remote node running a bitcoin peer. Example syntax is:
|
||||||
|
|
||||||
|
// Reads and validates the next bitcoin message from conn using the
|
||||||
|
// protocol version pver and the bitcoin network btcnet. The returns
|
||||||
|
// are a wire.Message, a []byte which contains the unmarshalled
|
||||||
|
// raw payload, and a possible error.
|
||||||
|
msg, rawPayload, err := wire.ReadMessage(conn, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
|
||||||
|
Writing Messages
|
||||||
|
|
||||||
|
In order to marshall bitcoin messages to the wire, use the WriteMessage
|
||||||
|
function. It accepts any io.Writer, but typically this will be a net.Conn to
|
||||||
|
a remote node running a bitcoin peer. Example syntax to request addresses
|
||||||
|
from a remote peer is:
|
||||||
|
|
||||||
|
// Create a new getaddr bitcoin message.
|
||||||
|
msg := wire.NewMsgGetAddr()
|
||||||
|
|
||||||
|
// Writes a bitcoin message msg to conn using the protocol version
|
||||||
|
// pver, and the bitcoin network btcnet. The return is a possible
|
||||||
|
// error.
|
||||||
|
err := wire.WriteMessage(conn, msg, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Errors returned by this package are either the raw errors provided by underlying
|
||||||
|
calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and
|
||||||
|
io.ErrShortWrite, or of type wire.MessageError. This allows the caller to
|
||||||
|
differentiate between general IO errors and malformed messages through type
|
||||||
|
assertions.
|
||||||
|
|
||||||
|
Bitcoin Improvement Proposals
|
||||||
|
|
||||||
|
This package includes spec changes outlined by the following BIPs:
|
||||||
|
|
||||||
|
BIP0014 (https://en.bitcoin.it/wiki/BIP_0014)
|
||||||
|
BIP0031 (https://en.bitcoin.it/wiki/BIP_0031)
|
||||||
|
BIP0035 (https://en.bitcoin.it/wiki/BIP_0035)
|
||||||
|
BIP0037 (https://en.bitcoin.it/wiki/BIP_0037)
|
||||||
|
*/
|
||||||
|
package wire
|
34
wire/error.go
Normal file
34
wire/error.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageError describes an issue with a message.
|
||||||
|
// An example of some potential issues are messages from the wrong bitcoin
|
||||||
|
// network, invalid commands, mismatched checksums, and exceeding max payloads.
|
||||||
|
//
|
||||||
|
// This provides a mechanism for the caller to type assert the error to
|
||||||
|
// differentiate between general io errors such as io.EOF and issues that
|
||||||
|
// resulted from malformed messages.
|
||||||
|
type MessageError struct {
|
||||||
|
Func string // Function name
|
||||||
|
Description string // Human readable description of the issue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfies the error interface and prints human-readable errors.
|
||||||
|
func (e *MessageError) Error() string {
|
||||||
|
if e.Func != "" {
|
||||||
|
return fmt.Sprintf("%v: %v", e.Func, e.Description)
|
||||||
|
}
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageError creates an error for the given function and description.
|
||||||
|
func messageError(f string, desc string) *MessageError {
|
||||||
|
return &MessageError{Func: f, Description: desc}
|
||||||
|
}
|
62
wire/fakeconn_test.go
Normal file
62
wire/fakeconn_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeConn implements the net.Conn interface and is used to test functions
|
||||||
|
// which work with a net.Conn without having to actually make any real
|
||||||
|
// connections.
|
||||||
|
type fakeConn struct {
|
||||||
|
localAddr net.Addr
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Write(b []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the localAddr field of the fake connection and satisfies
|
||||||
|
// the net.Conn interface.
|
||||||
|
func (c *fakeConn) LocalAddr() net.Addr {
|
||||||
|
return c.localAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remoteAddr field of the fake connection and satisfies
|
||||||
|
// the net.Conn interface.
|
||||||
|
func (c *fakeConn) RemoteAddr() net.Addr {
|
||||||
|
return c.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline doesn't do anything. It just satisfies the net.Conn
|
||||||
|
// interface.
|
||||||
|
func (c *fakeConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline doesn't do anything. It just satisfies the net.Conn
|
||||||
|
// interface.
|
||||||
|
func (c *fakeConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
60
wire/fakemessage_test.go
Normal file
60
wire/fakemessage_test.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeMessage implements the wire.Message interface and is used to force
|
||||||
|
// encode errors in messages.
|
||||||
|
type fakeMessage struct {
|
||||||
|
command string
|
||||||
|
payload []byte
|
||||||
|
forceEncodeErr bool
|
||||||
|
forceLenErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode doesn't do anything. It just satisfies the wire.Message
|
||||||
|
// interface.
|
||||||
|
func (msg *fakeMessage) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode writes the payload field of the fake message or forces an error
|
||||||
|
// if the forceEncodeErr flag of the fake message is set. It also satisfies the
|
||||||
|
// wire.Message interface.
|
||||||
|
func (msg *fakeMessage) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if msg.forceEncodeErr {
|
||||||
|
err := &wire.MessageError{
|
||||||
|
Func: "fakeMessage.BtcEncode",
|
||||||
|
Description: "intentional error",
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.Write(msg.payload)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the command field of the fake message and satisfies the
|
||||||
|
// wire.Message interface.
|
||||||
|
func (msg *fakeMessage) Command() string {
|
||||||
|
return msg.command
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the length of the payload field of fake message
|
||||||
|
// or a smaller value if the forceLenErr flag of the fake message is set. It
|
||||||
|
// satisfies the wire.Message interface.
|
||||||
|
func (msg *fakeMessage) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
lenp := uint32(len(msg.payload))
|
||||||
|
if msg.forceLenErr {
|
||||||
|
return lenp - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return lenp
|
||||||
|
}
|
77
wire/fixedIO_test.go
Normal file
77
wire/fixedIO_test.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fixedWriter implements the io.Writer interface and intentially allows
|
||||||
|
// testing of error paths by forcing short writes.
|
||||||
|
type fixedWriter struct {
|
||||||
|
b []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the contents of p to w. When the contents of p would cause
|
||||||
|
// the writer to exceed the maximum allowed size of the fixed writer,
|
||||||
|
// io.ErrShortWrite is returned and the writer is left unchanged.
|
||||||
|
//
|
||||||
|
// This satisfies the io.Writer interface.
|
||||||
|
func (w *fixedWriter) Write(p []byte) (n int, err error) {
|
||||||
|
lenp := len(p)
|
||||||
|
if w.pos+lenp > cap(w.b) {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
n = lenp
|
||||||
|
w.pos += copy(w.b[w.pos:], p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes alreayd written to the fixed writer.
|
||||||
|
func (w *fixedWriter) Bytes() []byte {
|
||||||
|
return w.b
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFixedWriter returns a new io.Writer that will error once more bytes than
|
||||||
|
// the specified max have been written.
|
||||||
|
func newFixedWriter(max int) io.Writer {
|
||||||
|
b := make([]byte, max, max)
|
||||||
|
fw := fixedWriter{b, 0}
|
||||||
|
return &fw
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixedReader implements the io.Reader interface and intentially allows
|
||||||
|
// testing of error paths by forcing short reads.
|
||||||
|
type fixedReader struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
iobuf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads the next len(p) bytes from the fixed reader. When the number of
|
||||||
|
// bytes read would exceed the maximum number of allowed bytes to be read from
|
||||||
|
// the fixed writer, an error is returned.
|
||||||
|
//
|
||||||
|
// This satisfies the io.Reader interface.
|
||||||
|
func (fr *fixedReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = fr.iobuf.Read(p)
|
||||||
|
fr.pos += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFixedReader returns a new io.Reader that will error once more bytes than
|
||||||
|
// the specified max have been read.
|
||||||
|
func newFixedReader(max int, buf []byte) io.Reader {
|
||||||
|
b := make([]byte, max, max)
|
||||||
|
if buf != nil {
|
||||||
|
copy(b[:], buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
iobuf := bytes.NewBuffer(b)
|
||||||
|
fr := fixedReader{b, 0, iobuf}
|
||||||
|
return &fr
|
||||||
|
}
|
130
wire/internal_test.go
Normal file
130
wire/internal_test.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the wire package rather than than the wire_test
|
||||||
|
package so it can bridge access to the internals to properly test cases which
|
||||||
|
are either not possible or can't reliably be tested via the public interface.
|
||||||
|
The functions are only exported while the tests are being run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxTxPerBlock makes the internal maxTxPerBlock constant available to
|
||||||
|
// the test package.
|
||||||
|
MaxTxPerBlock = maxTxPerBlock
|
||||||
|
|
||||||
|
// MaxFlagsPerMerkleBlock makes the internal maxFlagsPerMerkleBlock
|
||||||
|
// constant available to the test package.
|
||||||
|
MaxFlagsPerMerkleBlock = maxFlagsPerMerkleBlock
|
||||||
|
|
||||||
|
// MaxCountSetCancel makes the internal maxCountSetCancel constant
|
||||||
|
// available to the test package.
|
||||||
|
MaxCountSetCancel = maxCountSetCancel
|
||||||
|
|
||||||
|
// MaxCountSetSubVer makes the internal maxCountSetSubVer constant
|
||||||
|
// available to the test package.
|
||||||
|
MaxCountSetSubVer = maxCountSetSubVer
|
||||||
|
)
|
||||||
|
|
||||||
|
// TstRandomUint64 makes the internal randomUint64 function available to the
|
||||||
|
// test package.
|
||||||
|
func TstRandomUint64(r io.Reader) (uint64, error) {
|
||||||
|
return randomUint64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadElement makes the internal readElement function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadElement(r io.Reader, element interface{}) error {
|
||||||
|
return readElement(r, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteElement makes the internal writeElement function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteElement(w io.Writer, element interface{}) error {
|
||||||
|
return writeElement(w, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadVarInt makes the internal readVarInt function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadVarInt(r io.Reader, pver uint32) (uint64, error) {
|
||||||
|
return readVarInt(r, pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteVarInt makes the internal writeVarInt function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteVarInt(w io.Writer, pver uint32, val uint64) error {
|
||||||
|
return writeVarInt(w, pver, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadVarString makes the internal readVarString function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadVarString(r io.Reader, pver uint32) (string, error) {
|
||||||
|
return readVarString(r, pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteVarString makes the internal writeVarString function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteVarString(w io.Writer, pver uint32, str string) error {
|
||||||
|
return writeVarString(w, pver, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadVarBytes makes the internal readVarBytes function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) {
|
||||||
|
return readVarBytes(r, pver, maxAllowed, fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteVarBytes makes the internal writeVarBytes function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteVarBytes(w io.Writer, pver uint32, bytes []byte) error {
|
||||||
|
return writeVarBytes(w, pver, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadNetAddress makes the internal readNetAddress function available to
|
||||||
|
// the test package.
|
||||||
|
func TstReadNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
return readNetAddress(r, pver, na, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteNetAddress makes the internal writeNetAddress function available to
|
||||||
|
// the test package.
|
||||||
|
func TstWriteNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
return writeNetAddress(w, pver, na, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstMaxNetAddressPayload makes the internal maxNetAddressPayload function
|
||||||
|
// available to the test package.
|
||||||
|
func TstMaxNetAddressPayload(pver uint32) uint32 {
|
||||||
|
return maxNetAddressPayload(pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadInvVect makes the internal readInvVect function available to the test
|
||||||
|
// package.
|
||||||
|
func TstReadInvVect(r io.Reader, pver uint32, iv *InvVect) error {
|
||||||
|
return readInvVect(r, pver, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteInvVect makes the internal writeInvVect function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteInvVect(w io.Writer, pver uint32, iv *InvVect) error {
|
||||||
|
return writeInvVect(w, pver, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadBlockHeader makes the internal readBlockHeader function available to
|
||||||
|
// the test package.
|
||||||
|
func TstReadBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||||
|
return readBlockHeader(r, pver, bh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteBlockHeader makes the internal writeBlockHeader function available to
|
||||||
|
// the test package.
|
||||||
|
func TstWriteBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||||
|
return writeBlockHeader(w, pver, bh)
|
||||||
|
}
|
82
wire/invvect.go
Normal file
82
wire/invvect.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxInvPerMsg is the maximum number of inventory vectors that can be in a
|
||||||
|
// single bitcoin inv message.
|
||||||
|
MaxInvPerMsg = 50000
|
||||||
|
|
||||||
|
// Maximum payload size for an inventory vector.
|
||||||
|
maxInvVectPayload = 4 + HashSize
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvType represents the allowed types of inventory vectors. See InvVect.
|
||||||
|
type InvType uint32
|
||||||
|
|
||||||
|
// These constants define the various supported inventory vector types.
|
||||||
|
const (
|
||||||
|
InvTypeError InvType = 0
|
||||||
|
InvTypeTx InvType = 1
|
||||||
|
InvTypeBlock InvType = 2
|
||||||
|
InvTypeFilteredBlock InvType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of service flags back to their constant names for pretty printing.
|
||||||
|
var ivStrings = map[InvType]string{
|
||||||
|
InvTypeError: "ERROR",
|
||||||
|
InvTypeTx: "MSG_TX",
|
||||||
|
InvTypeBlock: "MSG_BLOCK",
|
||||||
|
InvTypeFilteredBlock: "MSG_FILTERED_BLOCK",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the InvType in human-readable form.
|
||||||
|
func (invtype InvType) String() string {
|
||||||
|
if s, ok := ivStrings[invtype]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("Unknown InvType (%d)", uint32(invtype))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvVect defines a bitcoin inventory vector which is used to describe data,
|
||||||
|
// as specified by the Type field, that a peer wants, has, or does not have to
|
||||||
|
// another peer.
|
||||||
|
type InvVect struct {
|
||||||
|
Type InvType // Type of data
|
||||||
|
Hash ShaHash // Hash of the data
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInvVect returns a new InvVect using the provided type and hash.
|
||||||
|
func NewInvVect(typ InvType, hash *ShaHash) *InvVect {
|
||||||
|
return &InvVect{
|
||||||
|
Type: typ,
|
||||||
|
Hash: *hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInvVect reads an encoded InvVect from r depending on the protocol
|
||||||
|
// version.
|
||||||
|
func readInvVect(r io.Reader, pver uint32, iv *InvVect) error {
|
||||||
|
err := readElements(r, &iv.Type, &iv.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInvVect serializes an InvVect to w depending on the protocol version.
|
||||||
|
func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error {
|
||||||
|
err := writeElements(w, iv.Type, &iv.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
269
wire/invvect_test.go
Normal file
269
wire/invvect_test.go
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInvVectStringer tests the stringized output for inventory vector types.
|
||||||
|
func TestInvTypeStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.InvType
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{wire.InvTypeError, "ERROR"},
|
||||||
|
{wire.InvTypeTx, "MSG_TX"},
|
||||||
|
{wire.InvTypeBlock, "MSG_BLOCK"},
|
||||||
|
{0xffffffff, "Unknown InvType (4294967295)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvVect tests the InvVect API.
|
||||||
|
func TestInvVect(t *testing.T) {
|
||||||
|
ivType := wire.InvTypeBlock
|
||||||
|
hash := wire.ShaHash{}
|
||||||
|
|
||||||
|
// Ensure we get the same payload and signature back out.
|
||||||
|
iv := wire.NewInvVect(ivType, &hash)
|
||||||
|
if iv.Type != ivType {
|
||||||
|
t.Errorf("NewInvVect: wrong type - got %v, want %v",
|
||||||
|
iv.Type, ivType)
|
||||||
|
}
|
||||||
|
if !iv.Hash.IsEqual(&hash) {
|
||||||
|
t.Errorf("NewInvVect: wrong hash - got %v, want %v",
|
||||||
|
spew.Sdump(iv.Hash), spew.Sdump(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvVectWire tests the InvVect wire encode and decode for various
|
||||||
|
// protocol versions and supported inventory vector types.
|
||||||
|
func TestInvVectWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
baseHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// errInvVect is an inventory vector with an error.
|
||||||
|
errInvVect := wire.InvVect{
|
||||||
|
Type: wire.InvTypeError,
|
||||||
|
Hash: wire.ShaHash{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// errInvVectEncoded is the wire encoded bytes of errInvVect.
|
||||||
|
errInvVectEncoded := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x00, // InvTypeError
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // No hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// txInvVect is an inventory vector representing a transaction.
|
||||||
|
txInvVect := wire.InvVect{
|
||||||
|
Type: wire.InvTypeTx,
|
||||||
|
Hash: *baseHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// txInvVectEncoded is the wire encoded bytes of txInvVect.
|
||||||
|
txInvVectEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvTypeTx
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockInvVect is an inventory vector representing a block.
|
||||||
|
blockInvVect := wire.InvVect{
|
||||||
|
Type: wire.InvTypeBlock,
|
||||||
|
Hash: *baseHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockInvVectEncoded is the wire encoded bytes of blockInvVect.
|
||||||
|
blockInvVectEncoded := []byte{
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in wire.InvVect // NetAddress to encode
|
||||||
|
out wire.InvVect // Expected decoded NetAddress
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteInvVect(&buf, test.pver, &test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeInvVect #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeInvVect #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var iv wire.InvVect
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = wire.TstReadInvVect(rbuf, test.pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readInvVect #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iv, test.out) {
|
||||||
|
t.Errorf("readInvVect #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(iv), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
369
wire/message.go
Normal file
369
wire/message.go
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageHeaderSize is the number of bytes in a bitcoin message header.
|
||||||
|
// Bitcoin network (magic) 4 bytes + command 12 bytes + payload length 4 bytes +
|
||||||
|
// checksum 4 bytes.
|
||||||
|
const MessageHeaderSize = 24
|
||||||
|
|
||||||
|
// CommandSize is the fixed size of all commands in the common bitcoin message
|
||||||
|
// header. Shorter commands must be zero padded.
|
||||||
|
const CommandSize = 12
|
||||||
|
|
||||||
|
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
||||||
|
// individual limits imposed by messages themselves.
|
||||||
|
const MaxMessagePayload = (1024 * 1024 * 32) // 32MB
|
||||||
|
|
||||||
|
// Commands used in bitcoin message headers which describe the type of message.
|
||||||
|
const (
|
||||||
|
CmdVersion = "version"
|
||||||
|
CmdVerAck = "verack"
|
||||||
|
CmdGetAddr = "getaddr"
|
||||||
|
CmdAddr = "addr"
|
||||||
|
CmdGetBlocks = "getblocks"
|
||||||
|
CmdInv = "inv"
|
||||||
|
CmdGetData = "getdata"
|
||||||
|
CmdNotFound = "notfound"
|
||||||
|
CmdBlock = "block"
|
||||||
|
CmdTx = "tx"
|
||||||
|
CmdGetHeaders = "getheaders"
|
||||||
|
CmdHeaders = "headers"
|
||||||
|
CmdPing = "ping"
|
||||||
|
CmdPong = "pong"
|
||||||
|
CmdAlert = "alert"
|
||||||
|
CmdMemPool = "mempool"
|
||||||
|
CmdFilterAdd = "filteradd"
|
||||||
|
CmdFilterClear = "filterclear"
|
||||||
|
CmdFilterLoad = "filterload"
|
||||||
|
CmdMerkleBlock = "merkleblock"
|
||||||
|
CmdReject = "reject"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is an interface that describes a bitcoin message. A type that
|
||||||
|
// implements Message has complete control over the representation of its data
|
||||||
|
// and may therefore contain additional or fewer fields than those which
|
||||||
|
// are used directly in the protocol encoded message.
|
||||||
|
type Message interface {
|
||||||
|
BtcDecode(io.Reader, uint32) error
|
||||||
|
BtcEncode(io.Writer, uint32) error
|
||||||
|
Command() string
|
||||||
|
MaxPayloadLength(uint32) uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeEmptyMessage creates a message of the appropriate concrete type based
|
||||||
|
// on the command.
|
||||||
|
func makeEmptyMessage(command string) (Message, error) {
|
||||||
|
var msg Message
|
||||||
|
switch command {
|
||||||
|
case CmdVersion:
|
||||||
|
msg = &MsgVersion{}
|
||||||
|
|
||||||
|
case CmdVerAck:
|
||||||
|
msg = &MsgVerAck{}
|
||||||
|
|
||||||
|
case CmdGetAddr:
|
||||||
|
msg = &MsgGetAddr{}
|
||||||
|
|
||||||
|
case CmdAddr:
|
||||||
|
msg = &MsgAddr{}
|
||||||
|
|
||||||
|
case CmdGetBlocks:
|
||||||
|
msg = &MsgGetBlocks{}
|
||||||
|
|
||||||
|
case CmdBlock:
|
||||||
|
msg = &MsgBlock{}
|
||||||
|
|
||||||
|
case CmdInv:
|
||||||
|
msg = &MsgInv{}
|
||||||
|
|
||||||
|
case CmdGetData:
|
||||||
|
msg = &MsgGetData{}
|
||||||
|
|
||||||
|
case CmdNotFound:
|
||||||
|
msg = &MsgNotFound{}
|
||||||
|
|
||||||
|
case CmdTx:
|
||||||
|
msg = &MsgTx{}
|
||||||
|
|
||||||
|
case CmdPing:
|
||||||
|
msg = &MsgPing{}
|
||||||
|
|
||||||
|
case CmdPong:
|
||||||
|
msg = &MsgPong{}
|
||||||
|
|
||||||
|
case CmdGetHeaders:
|
||||||
|
msg = &MsgGetHeaders{}
|
||||||
|
|
||||||
|
case CmdHeaders:
|
||||||
|
msg = &MsgHeaders{}
|
||||||
|
|
||||||
|
case CmdAlert:
|
||||||
|
msg = &MsgAlert{}
|
||||||
|
|
||||||
|
case CmdMemPool:
|
||||||
|
msg = &MsgMemPool{}
|
||||||
|
|
||||||
|
case CmdFilterAdd:
|
||||||
|
msg = &MsgFilterAdd{}
|
||||||
|
|
||||||
|
case CmdFilterClear:
|
||||||
|
msg = &MsgFilterClear{}
|
||||||
|
|
||||||
|
case CmdFilterLoad:
|
||||||
|
msg = &MsgFilterLoad{}
|
||||||
|
|
||||||
|
case CmdMerkleBlock:
|
||||||
|
msg = &MsgMerkleBlock{}
|
||||||
|
|
||||||
|
case CmdReject:
|
||||||
|
msg = &MsgReject{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unhandled command [%s]", command)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageHeader defines the header structure for all bitcoin protocol messages.
|
||||||
|
type messageHeader struct {
|
||||||
|
magic BitcoinNet // 4 bytes
|
||||||
|
command string // 12 bytes
|
||||||
|
length uint32 // 4 bytes
|
||||||
|
checksum [4]byte // 4 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMessageHeader reads a bitcoin message header from r.
|
||||||
|
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
||||||
|
// Since readElements doesn't return the amount of bytes read, attempt
|
||||||
|
// to read the entire header into a buffer first in case there is a
|
||||||
|
// short read so the proper amount of read bytes are known. This works
|
||||||
|
// since the header is a fixed size.
|
||||||
|
var headerBytes [MessageHeaderSize]byte
|
||||||
|
n, err := io.ReadFull(r, headerBytes[:])
|
||||||
|
if err != nil {
|
||||||
|
return n, nil, err
|
||||||
|
}
|
||||||
|
hr := bytes.NewReader(headerBytes[:])
|
||||||
|
|
||||||
|
// Create and populate a messageHeader struct from the raw header bytes.
|
||||||
|
hdr := messageHeader{}
|
||||||
|
var command [CommandSize]byte
|
||||||
|
readElements(hr, &hdr.magic, &command, &hdr.length, &hdr.checksum)
|
||||||
|
|
||||||
|
// Strip trailing zeros from command string.
|
||||||
|
hdr.command = string(bytes.TrimRight(command[:], string(0)))
|
||||||
|
|
||||||
|
return n, &hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardInput reads n bytes from reader r in chunks and discards the read
|
||||||
|
// bytes. This is used to skip payloads when various errors occur and helps
|
||||||
|
// prevent rogue nodes from causing massive memory allocation through forging
|
||||||
|
// header length.
|
||||||
|
func discardInput(r io.Reader, n uint32) {
|
||||||
|
maxSize := uint32(10 * 1024) // 10k at a time
|
||||||
|
numReads := n / maxSize
|
||||||
|
bytesRemaining := n % maxSize
|
||||||
|
if n > 0 {
|
||||||
|
buf := make([]byte, maxSize)
|
||||||
|
for i := uint32(0); i < numReads; i++ {
|
||||||
|
io.ReadFull(r, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bytesRemaining > 0 {
|
||||||
|
buf := make([]byte, bytesRemaining)
|
||||||
|
io.ReadFull(r, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMessageN writes a bitcoin Message to w including the necessary header
|
||||||
|
// information and returns the number of bytes written. This function is the
|
||||||
|
// same as WriteMessage except it also returns the number of bytes written.
|
||||||
|
func WriteMessageN(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) (int, error) {
|
||||||
|
totalBytes := 0
|
||||||
|
|
||||||
|
// Enforce max command size.
|
||||||
|
var command [CommandSize]byte
|
||||||
|
cmd := msg.Command()
|
||||||
|
if len(cmd) > CommandSize {
|
||||||
|
str := fmt.Sprintf("command [%s] is too long [max %v]",
|
||||||
|
cmd, CommandSize)
|
||||||
|
return totalBytes, messageError("WriteMessage", str)
|
||||||
|
}
|
||||||
|
copy(command[:], []byte(cmd))
|
||||||
|
|
||||||
|
// Encode the message payload.
|
||||||
|
var bw bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&bw, pver)
|
||||||
|
if err != nil {
|
||||||
|
return totalBytes, err
|
||||||
|
}
|
||||||
|
payload := bw.Bytes()
|
||||||
|
lenp := len(payload)
|
||||||
|
|
||||||
|
// Enforce maximum overall message payload.
|
||||||
|
if lenp > MaxMessagePayload {
|
||||||
|
str := fmt.Sprintf("message payload is too large - encoded "+
|
||||||
|
"%d bytes, but maximum message payload is %d bytes",
|
||||||
|
lenp, MaxMessagePayload)
|
||||||
|
return totalBytes, messageError("WriteMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce maximum message payload based on the message type.
|
||||||
|
mpl := msg.MaxPayloadLength(pver)
|
||||||
|
if uint32(lenp) > mpl {
|
||||||
|
str := fmt.Sprintf("message payload is too large - encoded "+
|
||||||
|
"%d bytes, but maximum message payload size for "+
|
||||||
|
"messages of type [%s] is %d.", lenp, cmd, mpl)
|
||||||
|
return totalBytes, messageError("WriteMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create header for the message.
|
||||||
|
hdr := messageHeader{}
|
||||||
|
hdr.magic = btcnet
|
||||||
|
hdr.command = cmd
|
||||||
|
hdr.length = uint32(lenp)
|
||||||
|
copy(hdr.checksum[:], DoubleSha256(payload)[0:4])
|
||||||
|
|
||||||
|
// Encode the header for the message. This is done to a buffer
|
||||||
|
// rather than directly to the writer since writeElements doesn't
|
||||||
|
// return the number of bytes written.
|
||||||
|
hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
|
||||||
|
writeElements(hw, hdr.magic, command, hdr.length, hdr.checksum)
|
||||||
|
|
||||||
|
// Write header.
|
||||||
|
n, err := w.Write(hw.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
totalBytes += n
|
||||||
|
return totalBytes, err
|
||||||
|
}
|
||||||
|
totalBytes += n
|
||||||
|
|
||||||
|
// Write payload.
|
||||||
|
n, err = w.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
totalBytes += n
|
||||||
|
return totalBytes, err
|
||||||
|
}
|
||||||
|
totalBytes += n
|
||||||
|
|
||||||
|
return totalBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMessage writes a bitcoin Message to w including the necessary header
|
||||||
|
// information. This function is the same as WriteMessageN except it doesn't
|
||||||
|
// doesn't return the number of bytes written. This function is mainly provided
|
||||||
|
// for backwards compatibility with the original API, but it's also useful for
|
||||||
|
// callers that don't care about byte counts.
|
||||||
|
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error {
|
||||||
|
_, err := WriteMessageN(w, msg, pver, btcnet)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMessageN reads, validates, and parses the next bitcoin Message from r for
|
||||||
|
// the provided protocol version and bitcoin network. It returns the number of
|
||||||
|
// bytes read in addition to the parsed Message and raw bytes which comprise the
|
||||||
|
// message. This function is the same as ReadMessage except it also returns the
|
||||||
|
// number of bytes read.
|
||||||
|
func ReadMessageN(r io.Reader, pver uint32, btcnet BitcoinNet) (int, Message, []byte, error) {
|
||||||
|
totalBytes := 0
|
||||||
|
n, hdr, err := readMessageHeader(r)
|
||||||
|
if err != nil {
|
||||||
|
totalBytes += n
|
||||||
|
return totalBytes, nil, nil, err
|
||||||
|
}
|
||||||
|
totalBytes += n
|
||||||
|
|
||||||
|
// Enforce maximum message payload.
|
||||||
|
if hdr.length > MaxMessagePayload {
|
||||||
|
str := fmt.Sprintf("message payload is too large - header "+
|
||||||
|
"indicates %d bytes, but max message payload is %d "+
|
||||||
|
"bytes.", hdr.length, MaxMessagePayload)
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for messages from the wrong bitcoin network.
|
||||||
|
if hdr.magic != btcnet {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
str := fmt.Sprintf("message from other network [%v]", hdr.magic)
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for malformed commands.
|
||||||
|
command := hdr.command
|
||||||
|
if !utf8.ValidString(command) {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
str := fmt.Sprintf("invalid command %v", []byte(command))
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create struct of appropriate message type based on the command.
|
||||||
|
msg, err := makeEmptyMessage(command)
|
||||||
|
if err != nil {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage",
|
||||||
|
err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for maximum length based on the message type as a malicious client
|
||||||
|
// could otherwise create a well-formed header and set the length to max
|
||||||
|
// numbers in order to exhaust the machine's memory.
|
||||||
|
mpl := msg.MaxPayloadLength(pver)
|
||||||
|
if hdr.length > mpl {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
str := fmt.Sprintf("payload exceeds max length - header "+
|
||||||
|
"indicates %v bytes, but max payload size for "+
|
||||||
|
"messages of type [%v] is %v.", hdr.length, command, mpl)
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload.
|
||||||
|
payload := make([]byte, hdr.length)
|
||||||
|
n, err = io.ReadFull(r, payload)
|
||||||
|
if err != nil {
|
||||||
|
totalBytes += n
|
||||||
|
return totalBytes, nil, nil, err
|
||||||
|
}
|
||||||
|
totalBytes += n
|
||||||
|
|
||||||
|
// Test checksum.
|
||||||
|
checksum := DoubleSha256(payload)[0:4]
|
||||||
|
if !bytes.Equal(checksum[:], hdr.checksum[:]) {
|
||||||
|
str := fmt.Sprintf("payload checksum failed - header "+
|
||||||
|
"indicates %v, but actual checksum is %v.",
|
||||||
|
hdr.checksum, checksum)
|
||||||
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal message. NOTE: This must be a *bytes.Buffer since the
|
||||||
|
// MsgVersion BtcDecode function requires it.
|
||||||
|
pr := bytes.NewBuffer(payload)
|
||||||
|
err = msg.BtcDecode(pr, pver)
|
||||||
|
if err != nil {
|
||||||
|
return totalBytes, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalBytes, msg, payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMessage reads, validates, and parses the next bitcoin Message from r for
|
||||||
|
// the provided protocol version and bitcoin network. It returns the parsed
|
||||||
|
// Message and raw bytes which comprise the message. This function only differs
|
||||||
|
// from ReadMessageN in that it doesn't return the number of bytes read. This
|
||||||
|
// function is mainly provided for backwards compatibility with the original
|
||||||
|
// API, but it's also useful for callers that don't care about byte counts.
|
||||||
|
func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) {
|
||||||
|
_, msg, buf, err := ReadMessageN(r, pver, btcnet)
|
||||||
|
return msg, buf, err
|
||||||
|
}
|
452
wire/message_test.go
Normal file
452
wire/message_test.go
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeHeader is a convenience function to make a message header in the form of
|
||||||
|
// a byte slice. It is used to force errors when reading messages.
|
||||||
|
func makeHeader(btcnet wire.BitcoinNet, command string,
|
||||||
|
payloadLen uint32, checksum uint32) []byte {
|
||||||
|
|
||||||
|
// The length of a bitcoin message header is 24 bytes.
|
||||||
|
// 4 byte magic number of the bitcoin network + 12 byte command + 4 byte
|
||||||
|
// payload length + 4 byte checksum.
|
||||||
|
buf := make([]byte, 24)
|
||||||
|
binary.LittleEndian.PutUint32(buf, uint32(btcnet))
|
||||||
|
copy(buf[4:], []byte(command))
|
||||||
|
binary.LittleEndian.PutUint32(buf[16:], payloadLen)
|
||||||
|
binary.LittleEndian.PutUint32(buf[20:], checksum)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMessage tests the Read/WriteMessage and Read/WriteMessageN API.
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Create the various types of messages to test.
|
||||||
|
|
||||||
|
// MsgVersion.
|
||||||
|
addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}
|
||||||
|
you, err := wire.NewNetAddress(addrYou, wire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
you.Timestamp = time.Time{} // Version message has zero value timestamp.
|
||||||
|
addrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
me, err := wire.NewNetAddress(addrMe, wire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
me.Timestamp = time.Time{} // Version message has zero value timestamp.
|
||||||
|
msgVersion := wire.NewMsgVersion(me, you, 123123, 0)
|
||||||
|
|
||||||
|
msgVerack := wire.NewMsgVerAck()
|
||||||
|
msgGetAddr := wire.NewMsgGetAddr()
|
||||||
|
msgAddr := wire.NewMsgAddr()
|
||||||
|
msgGetBlocks := wire.NewMsgGetBlocks(&wire.ShaHash{})
|
||||||
|
msgBlock := &blockOne
|
||||||
|
msgInv := wire.NewMsgInv()
|
||||||
|
msgGetData := wire.NewMsgGetData()
|
||||||
|
msgNotFound := wire.NewMsgNotFound()
|
||||||
|
msgTx := wire.NewMsgTx()
|
||||||
|
msgPing := wire.NewMsgPing(123123)
|
||||||
|
msgPong := wire.NewMsgPong(123123)
|
||||||
|
msgGetHeaders := wire.NewMsgGetHeaders()
|
||||||
|
msgHeaders := wire.NewMsgHeaders()
|
||||||
|
msgAlert := wire.NewMsgAlert([]byte("payload"), []byte("signature"))
|
||||||
|
msgMemPool := wire.NewMsgMemPool()
|
||||||
|
msgFilterAdd := wire.NewMsgFilterAdd([]byte{0x01})
|
||||||
|
msgFilterClear := wire.NewMsgFilterClear()
|
||||||
|
msgFilterLoad := wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone)
|
||||||
|
bh := wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 0, 0)
|
||||||
|
msgMerkleBlock := wire.NewMsgMerkleBlock(bh)
|
||||||
|
msgReject := wire.NewMsgReject("block", wire.RejectDuplicate, "duplicate block")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in wire.Message // Value to encode
|
||||||
|
out wire.Message // Expected decoded value
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
btcnet wire.BitcoinNet // Network to use for wire encoding
|
||||||
|
bytes int // Expected num bytes read/written
|
||||||
|
}{
|
||||||
|
{msgVersion, msgVersion, pver, wire.MainNet, 125},
|
||||||
|
{msgVerack, msgVerack, pver, wire.MainNet, 24},
|
||||||
|
{msgGetAddr, msgGetAddr, pver, wire.MainNet, 24},
|
||||||
|
{msgAddr, msgAddr, pver, wire.MainNet, 25},
|
||||||
|
{msgGetBlocks, msgGetBlocks, pver, wire.MainNet, 61},
|
||||||
|
{msgBlock, msgBlock, pver, wire.MainNet, 239},
|
||||||
|
{msgInv, msgInv, pver, wire.MainNet, 25},
|
||||||
|
{msgGetData, msgGetData, pver, wire.MainNet, 25},
|
||||||
|
{msgNotFound, msgNotFound, pver, wire.MainNet, 25},
|
||||||
|
{msgTx, msgTx, pver, wire.MainNet, 34},
|
||||||
|
{msgPing, msgPing, pver, wire.MainNet, 32},
|
||||||
|
{msgPong, msgPong, pver, wire.MainNet, 32},
|
||||||
|
{msgGetHeaders, msgGetHeaders, pver, wire.MainNet, 61},
|
||||||
|
{msgHeaders, msgHeaders, pver, wire.MainNet, 25},
|
||||||
|
{msgAlert, msgAlert, pver, wire.MainNet, 42},
|
||||||
|
{msgMemPool, msgMemPool, pver, wire.MainNet, 24},
|
||||||
|
{msgFilterAdd, msgFilterAdd, pver, wire.MainNet, 26},
|
||||||
|
{msgFilterClear, msgFilterClear, pver, wire.MainNet, 24},
|
||||||
|
{msgFilterLoad, msgFilterLoad, pver, wire.MainNet, 35},
|
||||||
|
{msgMerkleBlock, msgMerkleBlock, pver, wire.MainNet, 110},
|
||||||
|
{msgReject, msgReject, pver, wire.MainNet, 79},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
nw, err := wire.WriteMessageN(&buf, test.in, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("WriteMessage #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the number of bytes written match the expected value.
|
||||||
|
if nw != test.bytes {
|
||||||
|
t.Errorf("WriteMessage #%d unexpected num bytes "+
|
||||||
|
"written - got %d, want %d", i, nw, test.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(buf.Bytes())
|
||||||
|
nr, msg, _, err := wire.ReadMessageN(rbuf, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ReadMessage #%d error %v, msg %v", i, err,
|
||||||
|
spew.Sdump(msg))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("ReadMessage #%d\n got: %v want: %v", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the number of bytes read match the expected value.
|
||||||
|
if nr != test.bytes {
|
||||||
|
t.Errorf("ReadMessage #%d unexpected num bytes read - "+
|
||||||
|
"got %d, want %d", i, nr, test.bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same thing for Read/WriteMessage, but ignore the bytes since
|
||||||
|
// they don't return them.
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.WriteMessage(&buf, test.in, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("WriteMessage #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewReader(buf.Bytes())
|
||||||
|
msg, _, err := wire.ReadMessage(rbuf, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ReadMessage #%d error %v, msg %v", i, err,
|
||||||
|
spew.Sdump(msg))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("ReadMessage #%d\n got: %v want: %v", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadMessageWireErrors performs negative tests against wire decoding into
|
||||||
|
// concrete messages to confirm error paths work correctly.
|
||||||
|
func TestReadMessageWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
btcnet := wire.MainNet
|
||||||
|
|
||||||
|
// Ensure message errors are as expected with no function specified.
|
||||||
|
wantErr := "something bad happened"
|
||||||
|
testErr := wire.MessageError{Description: wantErr}
|
||||||
|
if testErr.Error() != wantErr {
|
||||||
|
t.Errorf("MessageError: wrong error - got %v, want %v",
|
||||||
|
testErr.Error(), wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure message errors are as expected with a function specified.
|
||||||
|
wantFunc := "foo"
|
||||||
|
testErr = wire.MessageError{Func: wantFunc, Description: wantErr}
|
||||||
|
if testErr.Error() != wantFunc+": "+wantErr {
|
||||||
|
t.Errorf("MessageError: wrong error - got %v, want %v",
|
||||||
|
testErr.Error(), wantErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire encoded bytes for main and testnet3 networks magic identifiers.
|
||||||
|
testNet3Bytes := makeHeader(wire.TestNet3, "", 0, 0)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message that exceeds max overall message
|
||||||
|
// length.
|
||||||
|
mpl := uint32(wire.MaxMessagePayload)
|
||||||
|
exceedMaxPayloadBytes := makeHeader(btcnet, "getaddr", mpl+1, 0)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a command which is invalid utf-8.
|
||||||
|
badCommandBytes := makeHeader(btcnet, "bogus", 0, 0)
|
||||||
|
badCommandBytes[4] = 0x81
|
||||||
|
|
||||||
|
// Wire encoded bytes for a command which is valid, but not supported.
|
||||||
|
unsupportedCommandBytes := makeHeader(btcnet, "bogus", 0, 0)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message which exceeds the max payload for
|
||||||
|
// a specific message type.
|
||||||
|
exceedTypePayloadBytes := makeHeader(btcnet, "getaddr", 1, 0)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message which does not deliver the full
|
||||||
|
// payload according to the header length.
|
||||||
|
shortPayloadBytes := makeHeader(btcnet, "version", 115, 0)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message with a bad checksum.
|
||||||
|
badChecksumBytes := makeHeader(btcnet, "version", 2, 0xbeef)
|
||||||
|
badChecksumBytes = append(badChecksumBytes, []byte{0x0, 0x0}...)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message which has a valid header, but is
|
||||||
|
// the wrong format. An addr starts with a varint of the number of
|
||||||
|
// contained in the message. Claim there is two, but don't provide
|
||||||
|
// them. At the same time, forge the header fields so the message is
|
||||||
|
// otherwise accurate.
|
||||||
|
badMessageBytes := makeHeader(btcnet, "addr", 1, 0xeaadc31c)
|
||||||
|
badMessageBytes = append(badMessageBytes, 0x2)
|
||||||
|
|
||||||
|
// Wire encoded bytes for a message which the header claims has 15k
|
||||||
|
// bytes of data to discard.
|
||||||
|
discardBytes := makeHeader(btcnet, "bogus", 15*1024, 0)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
btcnet wire.BitcoinNet // Bitcoin network for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
readErr error // Expected read error
|
||||||
|
bytes int // Expected num bytes read
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read errors.
|
||||||
|
|
||||||
|
// Short header.
|
||||||
|
{
|
||||||
|
[]byte{},
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
0,
|
||||||
|
io.EOF,
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Wrong network. Want MainNet, but giving TestNet3.
|
||||||
|
{
|
||||||
|
testNet3Bytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(testNet3Bytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Exceed max overall message payload length.
|
||||||
|
{
|
||||||
|
exceedMaxPayloadBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(exceedMaxPayloadBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invalid UTF-8 command.
|
||||||
|
{
|
||||||
|
badCommandBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(badCommandBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Valid, but unsupported command.
|
||||||
|
{
|
||||||
|
unsupportedCommandBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(unsupportedCommandBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Exceed max allowed payload for a message of a specific type.
|
||||||
|
{
|
||||||
|
exceedTypePayloadBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(exceedTypePayloadBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Message with a payload shorter than the header indicates.
|
||||||
|
{
|
||||||
|
shortPayloadBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(shortPayloadBytes),
|
||||||
|
io.EOF,
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Message with a bad checksum.
|
||||||
|
{
|
||||||
|
badChecksumBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(badChecksumBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
26,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Message with a valid header, but wrong format.
|
||||||
|
{
|
||||||
|
badMessageBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(badMessageBytes),
|
||||||
|
io.EOF,
|
||||||
|
25,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 15k bytes of data to discard.
|
||||||
|
{
|
||||||
|
discardBytes,
|
||||||
|
pver,
|
||||||
|
btcnet,
|
||||||
|
len(discardBytes),
|
||||||
|
&wire.MessageError{},
|
||||||
|
24,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
nr, _, _, err := wire.ReadMessageN(r, test.pver, test.btcnet)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||||
|
"want: %T", i, err, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the number of bytes written match the expected value.
|
||||||
|
if nr != test.bytes {
|
||||||
|
t.Errorf("ReadMessage #%d unexpected num bytes read - "+
|
||||||
|
"got %d, want %d", i, nr, test.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||||
|
"want: %v <%T>", i, err, err,
|
||||||
|
test.readErr, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWriteMessageWireErrors performs negative tests against wire encoding from
|
||||||
|
// concrete messages to confirm error paths work correctly.
|
||||||
|
func TestWriteMessageWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
btcnet := wire.MainNet
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Fake message with a command that is too long.
|
||||||
|
badCommandMsg := &fakeMessage{command: "somethingtoolong"}
|
||||||
|
|
||||||
|
// Fake message with a problem during encoding
|
||||||
|
encodeErrMsg := &fakeMessage{forceEncodeErr: true}
|
||||||
|
|
||||||
|
// Fake message that has payload which exceeds max overall message size.
|
||||||
|
exceedOverallPayload := make([]byte, wire.MaxMessagePayload+1)
|
||||||
|
exceedOverallPayloadErrMsg := &fakeMessage{payload: exceedOverallPayload}
|
||||||
|
|
||||||
|
// Fake message that has payload which exceeds max allowed per message.
|
||||||
|
exceedPayload := make([]byte, 1)
|
||||||
|
exceedPayloadErrMsg := &fakeMessage{payload: exceedPayload, forceLenErr: true}
|
||||||
|
|
||||||
|
// Fake message that is used to force errors in the header and payload
|
||||||
|
// writes.
|
||||||
|
bogusPayload := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
bogusMsg := &fakeMessage{command: "bogus", payload: bogusPayload}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
msg wire.Message // Message to encode
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
btcnet wire.BitcoinNet // Bitcoin network for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
err error // Expected error
|
||||||
|
bytes int // Expected num bytes written
|
||||||
|
}{
|
||||||
|
// Command too long.
|
||||||
|
{badCommandMsg, pver, btcnet, 0, wireErr, 0},
|
||||||
|
// Force error in payload encode.
|
||||||
|
{encodeErrMsg, pver, btcnet, 0, wireErr, 0},
|
||||||
|
// Force error due to exceeding max overall message payload size.
|
||||||
|
{exceedOverallPayloadErrMsg, pver, btcnet, 0, wireErr, 0},
|
||||||
|
// Force error due to exceeding max payload for message type.
|
||||||
|
{exceedPayloadErrMsg, pver, btcnet, 0, wireErr, 0},
|
||||||
|
// Force error in header write.
|
||||||
|
{bogusMsg, pver, btcnet, 0, io.ErrShortWrite, 0},
|
||||||
|
// Force error in payload write.
|
||||||
|
{bogusMsg, pver, btcnet, 24, io.ErrShortWrite, 24},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
nw, err := wire.WriteMessageN(w, test.msg, test.pver, test.btcnet)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("WriteMessage #%d wrong error got: %v <%T>, "+
|
||||||
|
"want: %T", i, err, err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the number of bytes written match the expected value.
|
||||||
|
if nw != test.bytes {
|
||||||
|
t.Errorf("WriteMessage #%d unexpected num bytes "+
|
||||||
|
"written - got %d, want %d", i, nw, test.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||||
|
"want: %v <%T>", i, err, err,
|
||||||
|
test.err, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
142
wire/msgaddr.go
Normal file
142
wire/msgaddr.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxAddrPerMsg is the maximum number of addresses that can be in a single
|
||||||
|
// bitcoin addr message (MsgAddr).
|
||||||
|
const MaxAddrPerMsg = 1000
|
||||||
|
|
||||||
|
// MsgAddr implements the Message interface and represents a bitcoin
|
||||||
|
// addr message. It is used to provide a list of known active peers on the
|
||||||
|
// network. An active peer is considered one that has transmitted a message
|
||||||
|
// within the last 3 hours. Nodes which have not transmitted in that time
|
||||||
|
// frame should be forgotten. Each message is limited to a maximum number of
|
||||||
|
// addresses, which is currently 1000. As a result, multiple messages must
|
||||||
|
// be used to relay the full list.
|
||||||
|
//
|
||||||
|
// Use the AddAddress function to build up the list of known addresses when
|
||||||
|
// sending an addr message to another peer.
|
||||||
|
type MsgAddr struct {
|
||||||
|
AddrList []*NetAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddress adds a known active peer to the message.
|
||||||
|
func (msg *MsgAddr) AddAddress(na *NetAddress) error {
|
||||||
|
if len(msg.AddrList)+1 > MaxAddrPerMsg {
|
||||||
|
str := fmt.Sprintf("too many addresses in message [max %v]",
|
||||||
|
MaxAddrPerMsg)
|
||||||
|
return messageError("MsgAddr.AddAddress", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddrList = append(msg.AddrList, na)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddresses adds multiple known active peers to the message.
|
||||||
|
func (msg *MsgAddr) AddAddresses(netAddrs ...*NetAddress) error {
|
||||||
|
for _, na := range netAddrs {
|
||||||
|
err := msg.AddAddress(na)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAddresses removes all addresses from the message.
|
||||||
|
func (msg *MsgAddr) ClearAddresses() {
|
||||||
|
msg.AddrList = []*NetAddress{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max addresses per message.
|
||||||
|
if count > MaxAddrPerMsg {
|
||||||
|
str := fmt.Sprintf("too many addresses for message "+
|
||||||
|
"[count %v, max %v]", count, MaxAddrPerMsg)
|
||||||
|
return messageError("MsgAddr.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddrList = make([]*NetAddress, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
na := NetAddress{}
|
||||||
|
err := readNetAddress(r, pver, &na, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddAddress(&na)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Protocol versions before MultipleAddressVersion only allowed 1 address
|
||||||
|
// per message.
|
||||||
|
count := len(msg.AddrList)
|
||||||
|
if pver < MultipleAddressVersion && count > 1 {
|
||||||
|
str := fmt.Sprintf("too many addresses for message of "+
|
||||||
|
"protocol version %v [count %v, max 1]", pver, count)
|
||||||
|
return messageError("MsgAddr.BtcEncode", str)
|
||||||
|
|
||||||
|
}
|
||||||
|
if count > MaxAddrPerMsg {
|
||||||
|
str := fmt.Sprintf("too many addresses for message "+
|
||||||
|
"[count %v, max %v]", count, MaxAddrPerMsg)
|
||||||
|
return messageError("MsgAddr.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, na := range msg.AddrList {
|
||||||
|
err = writeNetAddress(w, pver, na, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) Command() string {
|
||||||
|
return CmdAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
if pver < MultipleAddressVersion {
|
||||||
|
// Num addresses (varInt) + a single net addresses.
|
||||||
|
return MaxVarIntPayload + maxNetAddressPayload(pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
return MaxVarIntPayload + (MaxAddrPerMsg * maxNetAddressPayload(pver))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgAddr returns a new bitcoin addr message that conforms to the
|
||||||
|
// Message interface. See MsgAddr for details.
|
||||||
|
func NewMsgAddr() *MsgAddr {
|
||||||
|
return &MsgAddr{
|
||||||
|
AddrList: make([]*NetAddress, 0, MaxAddrPerMsg),
|
||||||
|
}
|
||||||
|
}
|
321
wire/msgaddr_test.go
Normal file
321
wire/msgaddr_test.go
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAddr tests the MsgAddr API.
|
||||||
|
func TestAddr(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "addr"
|
||||||
|
msg := wire.NewMsgAddr()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(30009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure NetAddresses are added properly.
|
||||||
|
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
na, err := wire.NewNetAddress(tcpAddr, wire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
err = msg.AddAddress(na)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddAddress: %v", err)
|
||||||
|
}
|
||||||
|
if msg.AddrList[0] != na {
|
||||||
|
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the address list is cleared properly.
|
||||||
|
msg.ClearAddresses()
|
||||||
|
if len(msg.AddrList) != 0 {
|
||||||
|
t.Errorf("ClearAddresses: address list is not empty - "+
|
||||||
|
"got %v [%v], want %v", len(msg.AddrList),
|
||||||
|
spew.Sprint(msg.AddrList[0]), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed addresses per message returns
|
||||||
|
// error.
|
||||||
|
for i := 0; i < wire.MaxAddrPerMsg+1; i++ {
|
||||||
|
err = msg.AddAddress(na)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddAddress: expected error on too many addresses " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
err = msg.AddAddresses(na)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddAddresses: expected error on too many addresses " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for protocol versions before
|
||||||
|
// timestamp was added to NetAddress.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
pver = wire.NetAddressTimeVersion - 1
|
||||||
|
wantPayload = uint32(26009)
|
||||||
|
maxPayload = msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for protocol versions before
|
||||||
|
// multiple addresses were allowed.
|
||||||
|
// Num addresses (varInt) + a single net addresses.
|
||||||
|
pver = wire.MultipleAddressVersion - 1
|
||||||
|
wantPayload = uint32(35)
|
||||||
|
maxPayload = msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddrWire tests the MsgAddr wire encode and decode for various numbers
|
||||||
|
// of addreses and protocol versions.
|
||||||
|
func TestAddrWire(t *testing.T) {
|
||||||
|
// A couple of NetAddresses to use for testing.
|
||||||
|
na := &wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
na2 := &wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8334,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty address message.
|
||||||
|
noAddr := wire.NewMsgAddr()
|
||||||
|
noAddrEncoded := []byte{
|
||||||
|
0x00, // Varint for number of addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address message with multiple addresses.
|
||||||
|
multiAddr := wire.NewMsgAddr()
|
||||||
|
multiAddr.AddAddresses(na, na2)
|
||||||
|
multiAddrEncoded := []byte{
|
||||||
|
0x02, // Varint for number of addresses
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8e, // Port 8334 in big-endian
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgAddr // Message to encode
|
||||||
|
out *wire.MsgAddr // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no addresses.
|
||||||
|
{
|
||||||
|
noAddr,
|
||||||
|
noAddr,
|
||||||
|
noAddrEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple addresses.
|
||||||
|
{
|
||||||
|
multiAddr,
|
||||||
|
multiAddr,
|
||||||
|
multiAddrEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion-1 with no addresses.
|
||||||
|
{
|
||||||
|
noAddr,
|
||||||
|
noAddr,
|
||||||
|
noAddrEncoded,
|
||||||
|
wire.MultipleAddressVersion - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgAddr
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddrWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgAddr to confirm error paths work correctly.
|
||||||
|
func TestAddrWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverMA := wire.MultipleAddressVersion
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// A couple of NetAddresses to use for testing.
|
||||||
|
na := &wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
na2 := &wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8334,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address message with multiple addresses.
|
||||||
|
baseAddr := wire.NewMsgAddr()
|
||||||
|
baseAddr.AddAddresses(na, na2)
|
||||||
|
baseAddrEncoded := []byte{
|
||||||
|
0x02, // Varint for number of addresses
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8e, // Port 8334 in big-endian
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed
|
||||||
|
// addresses.
|
||||||
|
maxAddr := wire.NewMsgAddr()
|
||||||
|
for i := 0; i < wire.MaxAddrPerMsg; i++ {
|
||||||
|
maxAddr.AddAddress(na)
|
||||||
|
}
|
||||||
|
maxAddr.AddrList = append(maxAddr.AddrList, na)
|
||||||
|
maxAddrEncoded := []byte{
|
||||||
|
0xfd, 0x03, 0xe9, // Varint for number of addresses (1001)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgAddr // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in addresses count
|
||||||
|
{baseAddr, baseAddrEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in address list.
|
||||||
|
{baseAddr, baseAddrEncoded, pver, 1, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max inventory vectors.
|
||||||
|
{maxAddr, maxAddrEncoded, pver, 3, wireErr, wireErr},
|
||||||
|
// Force error with greater than max inventory vectors for
|
||||||
|
// protocol versions before multiple addresses were allowed.
|
||||||
|
{maxAddr, maxAddrEncoded, pverMA - 1, 3, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgAddr
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
422
wire/msgalert.go
Normal file
422
wire/msgalert.go
Normal file
|
@ -0,0 +1,422 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgAlert contains a payload and a signature:
|
||||||
|
//
|
||||||
|
// ===============================================
|
||||||
|
// | Field | Data Type | Size |
|
||||||
|
// ===============================================
|
||||||
|
// | payload | []uchar | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | signature | []uchar | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
//
|
||||||
|
// Here payload is an Alert serialized into a byte array to ensure that
|
||||||
|
// versions using incompatible alert formats can still relay
|
||||||
|
// alerts among one another.
|
||||||
|
//
|
||||||
|
// An Alert is the payload deserialized as follows:
|
||||||
|
//
|
||||||
|
// ===============================================
|
||||||
|
// | Field | Data Type | Size |
|
||||||
|
// ===============================================
|
||||||
|
// | Version | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | RelayUntil | int64 | 8 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Expiration | int64 | 8 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | ID | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Cancel | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | SetCancel | set<int32> | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | MinVer | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | MaxVer | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | SetSubVer | set<string> | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Priority | int32 | 4 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Comment | string | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | StatusBar | string | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Reserved | string | ? |
|
||||||
|
// -----------------------------------------------
|
||||||
|
// | Total (Fixed) | 45 |
|
||||||
|
// -----------------------------------------------
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
// * string is a VarString i.e VarInt length followed by the string itself
|
||||||
|
// * set<string> is a VarInt followed by as many number of strings
|
||||||
|
// * set<int32> is a VarInt followed by as many number of ints
|
||||||
|
// * fixedAlertSize = 40 + 5*min(VarInt) = 40 + 5*1 = 45
|
||||||
|
//
|
||||||
|
// Now we can define bounds on Alert size, SetCancel and SetSubVer
|
||||||
|
|
||||||
|
// Fixed size of the alert payload
|
||||||
|
const fixedAlertSize = 45
|
||||||
|
|
||||||
|
// maxSignatureSize is the max size of an ECDSA signature.
|
||||||
|
// NOTE: Since this size is fixed and < 255, the size of VarInt required = 1.
|
||||||
|
const maxSignatureSize = 72
|
||||||
|
|
||||||
|
// maxAlertSize is the maximum size an alert.
|
||||||
|
//
|
||||||
|
// MessagePayload = VarInt(Alert) + Alert + VarInt(Signature) + Signature
|
||||||
|
// MaxMessagePayload = maxAlertSize + max(VarInt) + maxSignatureSize + 1
|
||||||
|
const maxAlertSize = MaxMessagePayload - maxSignatureSize - MaxVarIntPayload - 1
|
||||||
|
|
||||||
|
// maxCountSetCancel is the maximum number of cancel IDs that could possibly
|
||||||
|
// fit into a maximum size alert.
|
||||||
|
//
|
||||||
|
// maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string)
|
||||||
|
// for caculating maximum number of cancel IDs, set all other var sizes to 0
|
||||||
|
// maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(int32)
|
||||||
|
// x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4
|
||||||
|
const maxCountSetCancel = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 4
|
||||||
|
|
||||||
|
// maxCountSetSubVer is the maximum number of subversions that could possibly
|
||||||
|
// fit into a maximum size alert.
|
||||||
|
//
|
||||||
|
// maxAlertSize = fixedAlertSize + max(SetCancel) + max(SetSubVer) + 3*(string)
|
||||||
|
// for caculating maximum number of subversions, set all other var sizes to 0
|
||||||
|
// maxAlertSize = fixedAlertSize + (MaxVarIntPayload-1) + x*sizeOf(string)
|
||||||
|
// x = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / sizeOf(string)
|
||||||
|
// subversion would typically be something like "/Satoshi:0.7.2/" (15 bytes)
|
||||||
|
// so assuming < 255 bytes, sizeOf(string) = sizeOf(uint8) + 255 = 256
|
||||||
|
const maxCountSetSubVer = (maxAlertSize - fixedAlertSize - MaxVarIntPayload + 1) / 256
|
||||||
|
|
||||||
|
// Alert contains the data deserialized from the MsgAlert payload.
|
||||||
|
type Alert struct {
|
||||||
|
// Alert format version
|
||||||
|
Version int32
|
||||||
|
|
||||||
|
// Timestamp beyond which nodes should stop relaying this alert
|
||||||
|
RelayUntil int64
|
||||||
|
|
||||||
|
// Timestamp beyond which this alert is no longer in effect and
|
||||||
|
// should be ignored
|
||||||
|
Expiration int64
|
||||||
|
|
||||||
|
// A unique ID number for this alert
|
||||||
|
ID int32
|
||||||
|
|
||||||
|
// All alerts with an ID less than or equal to this number should
|
||||||
|
// cancelled, deleted and not accepted in the future
|
||||||
|
Cancel int32
|
||||||
|
|
||||||
|
// All alert IDs contained in this set should be cancelled as above
|
||||||
|
SetCancel []int32
|
||||||
|
|
||||||
|
// This alert only applies to versions greater than or equal to this
|
||||||
|
// version. Other versions should still relay it.
|
||||||
|
MinVer int32
|
||||||
|
|
||||||
|
// This alert only applies to versions less than or equal to this version.
|
||||||
|
// Other versions should still relay it.
|
||||||
|
MaxVer int32
|
||||||
|
|
||||||
|
// If this set contains any elements, then only nodes that have their
|
||||||
|
// subVer contained in this set are affected by the alert. Other versions
|
||||||
|
// should still relay it.
|
||||||
|
SetSubVer []string
|
||||||
|
|
||||||
|
// Relative priority compared to other alerts
|
||||||
|
Priority int32
|
||||||
|
|
||||||
|
// A comment on the alert that is not displayed
|
||||||
|
Comment string
|
||||||
|
|
||||||
|
// The alert message that is displayed to the user
|
||||||
|
StatusBar string
|
||||||
|
|
||||||
|
// Reserved
|
||||||
|
Reserved string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize encodes the alert to w using the alert protocol encoding format.
|
||||||
|
func (alert *Alert) Serialize(w io.Writer, pver uint32) error {
|
||||||
|
err := writeElements(w, alert.Version, alert.RelayUntil,
|
||||||
|
alert.Expiration, alert.ID, alert.Cancel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := len(alert.SetCancel)
|
||||||
|
if count > maxCountSetCancel {
|
||||||
|
str := fmt.Sprintf("too many cancel alert IDs for alert "+
|
||||||
|
"[count %v, max %v]", count, maxCountSetCancel)
|
||||||
|
return messageError("Alert.Serialize", str)
|
||||||
|
}
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
err = writeElement(w, alert.SetCancel[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElements(w, alert.MinVer, alert.MaxVer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count = len(alert.SetSubVer)
|
||||||
|
if count > maxCountSetSubVer {
|
||||||
|
str := fmt.Sprintf("too many sub versions for alert "+
|
||||||
|
"[count %v, max %v]", count, maxCountSetSubVer)
|
||||||
|
return messageError("Alert.Serialize", str)
|
||||||
|
}
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
err = writeVarString(w, pver, alert.SetSubVer[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, alert.Priority)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeVarString(w, pver, alert.Comment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeVarString(w, pver, alert.StatusBar)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeVarString(w, pver, alert.Reserved)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes from r into the receiver using the alert protocol
|
||||||
|
// encoding format.
|
||||||
|
func (alert *Alert) Deserialize(r io.Reader, pver uint32) error {
|
||||||
|
err := readElements(r, &alert.Version, &alert.RelayUntil,
|
||||||
|
&alert.Expiration, &alert.ID, &alert.Cancel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCancel: first read a VarInt that contains
|
||||||
|
// count - the number of Cancel IDs, then
|
||||||
|
// iterate count times and read them
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > maxCountSetCancel {
|
||||||
|
str := fmt.Sprintf("too many cancel alert IDs for alert "+
|
||||||
|
"[count %v, max %v]", count, maxCountSetCancel)
|
||||||
|
return messageError("Alert.Deserialize", str)
|
||||||
|
}
|
||||||
|
alert.SetCancel = make([]int32, count)
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
err := readElement(r, &alert.SetCancel[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElements(r, &alert.MinVer, &alert.MaxVer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSubVer: similar to SetCancel
|
||||||
|
// but read count number of sub-version strings
|
||||||
|
count, err = readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > maxCountSetSubVer {
|
||||||
|
str := fmt.Sprintf("too many sub versions for alert "+
|
||||||
|
"[count %v, max %v]", count, maxCountSetSubVer)
|
||||||
|
return messageError("Alert.Deserialize", str)
|
||||||
|
}
|
||||||
|
alert.SetSubVer = make([]string, count)
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
alert.SetSubVer[i], err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &alert.Priority)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
alert.Comment, err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
alert.StatusBar, err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
alert.Reserved, err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlert returns an new Alert with values provided.
|
||||||
|
func NewAlert(version int32, relayUntil int64, expiration int64,
|
||||||
|
id int32, cancel int32, setCancel []int32, minVer int32,
|
||||||
|
maxVer int32, setSubVer []string, priority int32, comment string,
|
||||||
|
statusBar string) *Alert {
|
||||||
|
return &Alert{
|
||||||
|
Version: version,
|
||||||
|
RelayUntil: relayUntil,
|
||||||
|
Expiration: expiration,
|
||||||
|
ID: id,
|
||||||
|
Cancel: cancel,
|
||||||
|
SetCancel: setCancel,
|
||||||
|
MinVer: minVer,
|
||||||
|
MaxVer: maxVer,
|
||||||
|
SetSubVer: setSubVer,
|
||||||
|
Priority: priority,
|
||||||
|
Comment: comment,
|
||||||
|
StatusBar: statusBar,
|
||||||
|
Reserved: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlertFromPayload returns an Alert with values deserialized from the
|
||||||
|
// serialized payload.
|
||||||
|
func NewAlertFromPayload(serializedPayload []byte, pver uint32) (*Alert, error) {
|
||||||
|
var alert Alert
|
||||||
|
r := bytes.NewReader(serializedPayload)
|
||||||
|
err := alert.Deserialize(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &alert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgAlert implements the Message interface and defines a bitcoin alert
|
||||||
|
// message.
|
||||||
|
//
|
||||||
|
// This is a signed message that provides notifications that the client should
|
||||||
|
// display if the signature matches the key. bitcoind/bitcoin-qt only checks
|
||||||
|
// against a signature from the core developers.
|
||||||
|
type MsgAlert struct {
|
||||||
|
// SerializedPayload is the alert payload serialized as a string so that the
|
||||||
|
// version can change but the Alert can still be passed on by older
|
||||||
|
// clients.
|
||||||
|
SerializedPayload []byte
|
||||||
|
|
||||||
|
// Signature is the ECDSA signature of the message.
|
||||||
|
Signature []byte
|
||||||
|
|
||||||
|
// Deserialized Payload
|
||||||
|
Payload *Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
msg.SerializedPayload, err = readVarBytes(r, pver, MaxMessagePayload,
|
||||||
|
"alert serialized payload")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Payload, err = NewAlertFromPayload(msg.SerializedPayload, pver)
|
||||||
|
if err != nil {
|
||||||
|
msg.Payload = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Signature, err = readVarBytes(r, pver, MaxMessagePayload,
|
||||||
|
"alert signature")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
var err error
|
||||||
|
var serializedpayload []byte
|
||||||
|
if msg.Payload != nil {
|
||||||
|
// try to Serialize Payload if possible
|
||||||
|
r := new(bytes.Buffer)
|
||||||
|
err = msg.Payload.Serialize(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
// Serialize failed - ignore & fallback
|
||||||
|
// to SerializedPayload
|
||||||
|
serializedpayload = msg.SerializedPayload
|
||||||
|
} else {
|
||||||
|
serializedpayload = r.Bytes()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serializedpayload = msg.SerializedPayload
|
||||||
|
}
|
||||||
|
slen := uint64(len(serializedpayload))
|
||||||
|
if slen == 0 {
|
||||||
|
return messageError("MsgAlert.BtcEncode", "empty serialized payload")
|
||||||
|
}
|
||||||
|
err = writeVarBytes(w, pver, serializedpayload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeVarBytes(w, pver, msg.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) Command() string {
|
||||||
|
return CmdAlert
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Since this can vary depending on the message, make it the max
|
||||||
|
// size allowed.
|
||||||
|
return MaxMessagePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgAlert returns a new bitcoin alert message that conforms to the Message
|
||||||
|
// interface. See MsgAlert for details.
|
||||||
|
func NewMsgAlert(serializedPayload []byte, signature []byte) *MsgAlert {
|
||||||
|
return &MsgAlert{
|
||||||
|
SerializedPayload: serializedPayload,
|
||||||
|
Signature: signature,
|
||||||
|
Payload: nil,
|
||||||
|
}
|
||||||
|
}
|
467
wire/msgalert_test.go
Normal file
467
wire/msgalert_test.go
Normal file
|
@ -0,0 +1,467 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMsgAlert tests the MsgAlert API.
|
||||||
|
func TestMsgAlert(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
serializedpayload := []byte("some message")
|
||||||
|
signature := []byte("some sig")
|
||||||
|
|
||||||
|
// Ensure we get the same payload and signature back out.
|
||||||
|
msg := wire.NewMsgAlert(serializedpayload, signature)
|
||||||
|
if !reflect.DeepEqual(msg.SerializedPayload, serializedpayload) {
|
||||||
|
t.Errorf("NewMsgAlert: wrong serializedpayload - got %v, want %v",
|
||||||
|
msg.SerializedPayload, serializedpayload)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg.Signature, signature) {
|
||||||
|
t.Errorf("NewMsgAlert: wrong signature - got %v, want %v",
|
||||||
|
msg.Signature, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "alert"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAlert: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(1024 * 1024 * 32)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test BtcEncode with Payload == nil
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
// expected = 0x0c + serializedpayload + 0x08 + signature
|
||||||
|
expectedBuf := append([]byte{0x0c}, serializedpayload...)
|
||||||
|
expectedBuf = append(expectedBuf, []byte{0x08}...)
|
||||||
|
expectedBuf = append(expectedBuf, signature...)
|
||||||
|
if !bytes.Equal(buf.Bytes(), expectedBuf) {
|
||||||
|
t.Errorf("BtcEncode got: %s want: %s",
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(expectedBuf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test BtcEncode with Payload != nil
|
||||||
|
// note: Payload is an empty Alert but not nil
|
||||||
|
msg.Payload = new(wire.Alert)
|
||||||
|
buf = *new(bytes.Buffer)
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
// empty Alert is 45 null bytes, see Alert comments
|
||||||
|
// for details
|
||||||
|
// expected = 0x2d + 45*0x00 + 0x08 + signature
|
||||||
|
expectedBuf = append([]byte{0x2d}, bytes.Repeat([]byte{0x00}, 45)...)
|
||||||
|
expectedBuf = append(expectedBuf, []byte{0x08}...)
|
||||||
|
expectedBuf = append(expectedBuf, signature...)
|
||||||
|
if !bytes.Equal(buf.Bytes(), expectedBuf) {
|
||||||
|
t.Errorf("BtcEncode got: %s want: %s",
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(expectedBuf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMsgAlertWire tests the MsgAlert wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestMsgAlertWire(t *testing.T) {
|
||||||
|
baseMsgAlert := wire.NewMsgAlert([]byte("some payload"), []byte("somesig"))
|
||||||
|
baseMsgAlertEncoded := []byte{
|
||||||
|
0x0c, // Varint for payload length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x79,
|
||||||
|
0x6c, 0x6f, 0x61, 0x64, // "some payload"
|
||||||
|
0x07, // Varint for signature length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x73, 0x69, 0x67, // "somesig"
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgAlert // Message to encode
|
||||||
|
out *wire.MsgAlert // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlertEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlertEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlertEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlertEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlert,
|
||||||
|
baseMsgAlertEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgAlert
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMsgAlertWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgAlert to confirm error paths work correctly.
|
||||||
|
func TestMsgAlertWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
baseMsgAlert := wire.NewMsgAlert([]byte("some payload"), []byte("somesig"))
|
||||||
|
baseMsgAlertEncoded := []byte{
|
||||||
|
0x0c, // Varint for payload length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x79,
|
||||||
|
0x6c, 0x6f, 0x61, 0x64, // "some payload"
|
||||||
|
0x07, // Varint for signature length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x73, 0x69, 0x67, // "somesig"
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgAlert // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in payload length.
|
||||||
|
{baseMsgAlert, baseMsgAlertEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in payload.
|
||||||
|
{baseMsgAlert, baseMsgAlertEncoded, pver, 1, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in signature length.
|
||||||
|
{baseMsgAlert, baseMsgAlertEncoded, pver, 13, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in signature.
|
||||||
|
{baseMsgAlert, baseMsgAlertEncoded, pver, 14, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgAlert
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Error on empty Payload
|
||||||
|
baseMsgAlert.SerializedPayload = []byte{}
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
err := baseMsgAlert.BtcEncode(w, pver)
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T",
|
||||||
|
err, wire.MessageError{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Payload Serialize error
|
||||||
|
// overflow the max number of elements in SetCancel
|
||||||
|
baseMsgAlert.Payload = new(wire.Alert)
|
||||||
|
baseMsgAlert.Payload.SetCancel = make([]int32, wire.MaxCountSetCancel+1)
|
||||||
|
buf := *new(bytes.Buffer)
|
||||||
|
err = baseMsgAlert.BtcEncode(&buf, pver)
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T",
|
||||||
|
err, wire.MessageError{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// overflow the max number of elements in SetSubVer
|
||||||
|
baseMsgAlert.Payload = new(wire.Alert)
|
||||||
|
baseMsgAlert.Payload.SetSubVer = make([]string, wire.MaxCountSetSubVer+1)
|
||||||
|
buf = *new(bytes.Buffer)
|
||||||
|
err = baseMsgAlert.BtcEncode(&buf, pver)
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("MsgAlert.BtcEncode wrong error got: %T, want: %T",
|
||||||
|
err, wire.MessageError{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAlert tests serialization and deserialization
|
||||||
|
// of the payload to Alert
|
||||||
|
func TestAlert(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
alert := wire.NewAlert(
|
||||||
|
1, 1337093712, 1368628812, 1015,
|
||||||
|
1013, []int32{1014}, 0, 40599, []string{"/Satoshi:0.7.2/"}, 5000, "",
|
||||||
|
"URGENT: upgrade required, see http://bitcoin.org/dos for details",
|
||||||
|
)
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
err := alert.Serialize(w, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
serializedpayload := w.Bytes()
|
||||||
|
newAlert, err := wire.NewAlertFromPayload(serializedpayload, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if alert.Version != newAlert.Version {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Version - got %v, want %v ",
|
||||||
|
alert.Version, newAlert.Version)
|
||||||
|
}
|
||||||
|
if alert.RelayUntil != newAlert.RelayUntil {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong RelayUntil - got %v, want %v ",
|
||||||
|
alert.RelayUntil, newAlert.RelayUntil)
|
||||||
|
}
|
||||||
|
if alert.Expiration != newAlert.Expiration {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Expiration - got %v, want %v ",
|
||||||
|
alert.Expiration, newAlert.Expiration)
|
||||||
|
}
|
||||||
|
if alert.ID != newAlert.ID {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong ID - got %v, want %v ",
|
||||||
|
alert.ID, newAlert.ID)
|
||||||
|
}
|
||||||
|
if alert.Cancel != newAlert.Cancel {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Cancel - got %v, want %v ",
|
||||||
|
alert.Cancel, newAlert.Cancel)
|
||||||
|
}
|
||||||
|
if len(alert.SetCancel) != len(newAlert.SetCancel) {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong number of SetCancel - got %v, want %v ",
|
||||||
|
len(alert.SetCancel), len(newAlert.SetCancel))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(alert.SetCancel); i++ {
|
||||||
|
if alert.SetCancel[i] != newAlert.SetCancel[i] {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong SetCancel[%v] - got %v, want %v ",
|
||||||
|
len(alert.SetCancel), alert.SetCancel[i], newAlert.SetCancel[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if alert.MinVer != newAlert.MinVer {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong MinVer - got %v, want %v ",
|
||||||
|
alert.MinVer, newAlert.MinVer)
|
||||||
|
}
|
||||||
|
if alert.MaxVer != newAlert.MaxVer {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong MaxVer - got %v, want %v ",
|
||||||
|
alert.MaxVer, newAlert.MaxVer)
|
||||||
|
}
|
||||||
|
if len(alert.SetSubVer) != len(newAlert.SetSubVer) {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong number of SetSubVer - got %v, want %v ",
|
||||||
|
len(alert.SetSubVer), len(newAlert.SetSubVer))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(alert.SetSubVer); i++ {
|
||||||
|
if alert.SetSubVer[i] != newAlert.SetSubVer[i] {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong SetSubVer[%v] - got %v, want %v ",
|
||||||
|
len(alert.SetSubVer), alert.SetSubVer[i], newAlert.SetSubVer[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if alert.Priority != newAlert.Priority {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Priority - got %v, want %v ",
|
||||||
|
alert.Priority, newAlert.Priority)
|
||||||
|
}
|
||||||
|
if alert.Comment != newAlert.Comment {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Comment - got %v, want %v ",
|
||||||
|
alert.Comment, newAlert.Comment)
|
||||||
|
}
|
||||||
|
if alert.StatusBar != newAlert.StatusBar {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong StatusBar - got %v, want %v ",
|
||||||
|
alert.StatusBar, newAlert.StatusBar)
|
||||||
|
}
|
||||||
|
if alert.Reserved != newAlert.Reserved {
|
||||||
|
t.Errorf("NewAlertFromPayload: wrong Reserved - got %v, want %v ",
|
||||||
|
alert.Reserved, newAlert.Reserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAlertErrors performs negative tests against payload serialization,
|
||||||
|
// deserialization of Alert to confirm error paths work correctly.
|
||||||
|
func TestAlertErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
baseAlert := wire.NewAlert(
|
||||||
|
1, 1337093712, 1368628812, 1015,
|
||||||
|
1013, []int32{1014}, 0, 40599, []string{"/Satoshi:0.7.2/"}, 5000, "",
|
||||||
|
"URGENT",
|
||||||
|
)
|
||||||
|
baseAlertEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0x01, 0xf6, 0x03, 0x00, //|................|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0x01, 0x0f, 0x2f, 0x53, 0x61, 0x74, 0x6f, //|.........../Sato|
|
||||||
|
0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......|
|
||||||
|
0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.|
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.Alert // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in Version
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in SetCancel VarInt.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 28, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in SetCancel ints.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 29, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in MinVer
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 40, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in SetSubVer string VarInt.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 41, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in SetSubVer strings.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 48, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in Priority
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 60, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in Comment string.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 62, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in StatusBar string.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 64, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in Reserved string.
|
||||||
|
{baseAlert, baseAlertEncoded, pver, 70, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.Serialize(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("Alert.Serialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var alert wire.Alert
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = alert.Deserialize(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("Alert.Deserialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overflow the max number of elements in SetCancel
|
||||||
|
// maxCountSetCancel + 1 == 8388575 == \xdf\xff\x7f\x00
|
||||||
|
// replace bytes 29-33
|
||||||
|
badAlertEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0xfe, 0xdf, 0xff, 0x7f, //|................|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0x01, 0x0f, 0x2f, 0x53, 0x61, 0x74, 0x6f, //|.........../Sato|
|
||||||
|
0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......|
|
||||||
|
0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.|
|
||||||
|
}
|
||||||
|
var alert wire.Alert
|
||||||
|
r := bytes.NewReader(badAlertEncoded)
|
||||||
|
err := alert.Deserialize(r, pver)
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("Alert.Deserialize wrong error got: %T, want: %T",
|
||||||
|
err, wire.MessageError{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// overflow the max number of elements in SetSubVer
|
||||||
|
// maxCountSetSubVer + 1 == 131071 + 1 == \x00\x00\x02\x00
|
||||||
|
// replace bytes 42-46
|
||||||
|
badAlertEncoded = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x50, 0x6e, 0xb2, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x9e, 0x93, 0x51, //|....Pn.O....L..Q|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xf7, 0x03, 0x00, 0x00, 0xf5, 0x03, 0x00, 0x00, 0x01, 0xf6, 0x03, 0x00, //|................|
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x9e, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x02, 0x00, 0x74, 0x6f, //|.........../Sato|
|
||||||
|
0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x37, 0x2e, 0x32, 0x2f, 0x88, 0x13, 0x00, 0x00, 0x00, 0x06, //|shi:0.7.2/......|
|
||||||
|
0x55, 0x52, 0x47, 0x45, 0x4e, 0x54, 0x00, //|URGENT.|
|
||||||
|
}
|
||||||
|
r = bytes.NewReader(badAlertEncoded)
|
||||||
|
err = alert.Deserialize(r, pver)
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("Alert.Deserialize wrong error got: %T, want: %T",
|
||||||
|
err, wire.MessageError{})
|
||||||
|
}
|
||||||
|
}
|
250
wire/msgblock.go
Normal file
250
wire/msgblock.go
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultTransactionAlloc is the default size used for the backing array
|
||||||
|
// for transactions. The transaction array will dynamically grow as needed, but
|
||||||
|
// this figure is intended to provide enough space for the number of
|
||||||
|
// transactions in the vast majority of blocks without needing to grow the
|
||||||
|
// backing array multiple times.
|
||||||
|
const defaultTransactionAlloc = 2048
|
||||||
|
|
||||||
|
// MaxBlocksPerMsg is the maximum number of blocks allowed per message.
|
||||||
|
const MaxBlocksPerMsg = 500
|
||||||
|
|
||||||
|
// MaxBlockPayload is the maximum bytes a block message can be in bytes.
|
||||||
|
const MaxBlockPayload = 1000000 // Not actually 1MB which would be 1024 * 1024
|
||||||
|
|
||||||
|
// maxTxPerBlock is the maximum number of transactions that could
|
||||||
|
// possibly fit into a block.
|
||||||
|
const maxTxPerBlock = (MaxBlockPayload / minTxPayload) + 1
|
||||||
|
|
||||||
|
// TxLoc holds locator data for the offset and length of where a transaction is
|
||||||
|
// located within a MsgBlock data buffer.
|
||||||
|
type TxLoc struct {
|
||||||
|
TxStart int
|
||||||
|
TxLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgBlock implements the Message interface and represents a bitcoin
|
||||||
|
// block message. It is used to deliver block and transaction information in
|
||||||
|
// response to a getdata message (MsgGetData) for a given block hash.
|
||||||
|
type MsgBlock struct {
|
||||||
|
Header BlockHeader
|
||||||
|
Transactions []*MsgTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTransaction adds a transaction to the message.
|
||||||
|
func (msg *MsgBlock) AddTransaction(tx *MsgTx) error {
|
||||||
|
msg.Transactions = append(msg.Transactions, tx)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTransactions removes all transactions from the message.
|
||||||
|
func (msg *MsgBlock) ClearTransactions() {
|
||||||
|
msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
// See Deserialize for decoding blocks stored to disk, such as in a database, as
|
||||||
|
// opposed to decoding blocks from the wire.
|
||||||
|
func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readBlockHeader(r, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
txCount, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent more transactions than could possibly fit into a block.
|
||||||
|
// It would be possible to cause memory exhaustion and panics without
|
||||||
|
// a sane upper bound on this count.
|
||||||
|
if txCount > maxTxPerBlock {
|
||||||
|
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||||
|
"[count %d, max %d]", txCount, maxTxPerBlock)
|
||||||
|
return messageError("MsgBlock.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||||
|
for i := uint64(0); i < txCount; i++ {
|
||||||
|
tx := MsgTx{}
|
||||||
|
err := tx.BtcDecode(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Transactions = append(msg.Transactions, &tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes a block from r into the receiver using a format that is
|
||||||
|
// suitable for long-term storage such as a database while respecting the
|
||||||
|
// Version field in the block. This function differs from BtcDecode in that
|
||||||
|
// BtcDecode decodes from the bitcoin wire protocol as it was sent across the
|
||||||
|
// network. The wire encoding can technically differ depending on the protocol
|
||||||
|
// version and doesn't even really need to match the format of a stored block at
|
||||||
|
// all. As of the time this comment was written, the encoded block is the same
|
||||||
|
// in both instances, but there is a distinct difference and separating the two
|
||||||
|
// allows the API to be flexible enough to deal with changes.
|
||||||
|
func (msg *MsgBlock) Deserialize(r io.Reader) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of BtcDecode.
|
||||||
|
return msg.BtcDecode(r, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes
|
||||||
|
// a byte buffer instead of a generic reader and returns a slice containing the start and length of
|
||||||
|
// each transaction within the raw data that is being deserialized.
|
||||||
|
func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) {
|
||||||
|
fullLen := r.Len()
|
||||||
|
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of existing wire protocol functions.
|
||||||
|
err := readBlockHeader(r, 0, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txCount, err := readVarInt(r, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent more transactions than could possibly fit into a block.
|
||||||
|
// It would be possible to cause memory exhaustion and panics without
|
||||||
|
// a sane upper bound on this count.
|
||||||
|
if txCount > maxTxPerBlock {
|
||||||
|
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||||
|
"[count %d, max %d]", txCount, maxTxPerBlock)
|
||||||
|
return nil, messageError("MsgBlock.DeserializeTxLoc", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize each transaction while keeping track of its location
|
||||||
|
// within the byte stream.
|
||||||
|
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||||
|
txLocs := make([]TxLoc, txCount)
|
||||||
|
for i := uint64(0); i < txCount; i++ {
|
||||||
|
txLocs[i].TxStart = fullLen - r.Len()
|
||||||
|
tx := MsgTx{}
|
||||||
|
err := tx.Deserialize(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Transactions = append(msg.Transactions, &tx)
|
||||||
|
txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart
|
||||||
|
}
|
||||||
|
|
||||||
|
return txLocs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
// See Serialize for encoding blocks to be stored to disk, such as in a
|
||||||
|
// database, as opposed to encoding blocks for the wire.
|
||||||
|
func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
err := writeBlockHeader(w, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(len(msg.Transactions)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range msg.Transactions {
|
||||||
|
err = tx.BtcEncode(w, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize encodes the block to w using a format that suitable for long-term
|
||||||
|
// storage such as a database while respecting the Version field in the block.
|
||||||
|
// This function differs from BtcEncode in that BtcEncode encodes the block to
|
||||||
|
// the bitcoin wire protocol in order to be sent across the network. The wire
|
||||||
|
// encoding can technically differ depending on the protocol version and doesn't
|
||||||
|
// even really need to match the format of a stored block at all. As of the
|
||||||
|
// time this comment was written, the encoded block is the same in both
|
||||||
|
// instances, but there is a distinct difference and separating the two allows
|
||||||
|
// the API to be flexible enough to deal with changes.
|
||||||
|
func (msg *MsgBlock) Serialize(w io.Writer) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of BtcEncode.
|
||||||
|
return msg.BtcEncode(w, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSize returns the number of bytes it would take to serialize the
|
||||||
|
// the block.
|
||||||
|
func (msg *MsgBlock) SerializeSize() int {
|
||||||
|
// Block header bytes + Serialized varint size for the number of
|
||||||
|
// transactions.
|
||||||
|
n := blockHeaderLen + VarIntSerializeSize(uint64(len(msg.Transactions)))
|
||||||
|
|
||||||
|
for _, tx := range msg.Transactions {
|
||||||
|
n += tx.SerializeSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) Command() string {
|
||||||
|
return CmdBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Block header at 80 bytes + transaction count + max transactions
|
||||||
|
// which can vary up to the MaxBlockPayload (including the block header
|
||||||
|
// and transaction count).
|
||||||
|
return MaxBlockPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSha computes the block identifier hash for this block.
|
||||||
|
func (msg *MsgBlock) BlockSha() (ShaHash, error) {
|
||||||
|
return msg.Header.BlockSha()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxShas returns a slice of hashes of all of transactions in this block.
|
||||||
|
func (msg *MsgBlock) TxShas() ([]ShaHash, error) {
|
||||||
|
shaList := make([]ShaHash, 0, len(msg.Transactions))
|
||||||
|
for _, tx := range msg.Transactions {
|
||||||
|
// Ignore error here since TxSha can't fail in the current
|
||||||
|
// implementation except due to run-time panics.
|
||||||
|
sha, _ := tx.TxSha()
|
||||||
|
shaList = append(shaList, sha)
|
||||||
|
}
|
||||||
|
return shaList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgBlock returns a new bitcoin block message that conforms to the
|
||||||
|
// Message interface. See MsgBlock for details.
|
||||||
|
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {
|
||||||
|
return &MsgBlock{
|
||||||
|
Header: *blockHeader,
|
||||||
|
Transactions: make([]*MsgTx, 0, defaultTransactionAlloc),
|
||||||
|
}
|
||||||
|
}
|
584
wire/msgblock_test.go
Normal file
584
wire/msgblock_test.go
Normal file
|
@ -0,0 +1,584 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBlock tests the MsgBlock API.
|
||||||
|
func TestBlock(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 1 header.
|
||||||
|
prevHash := &blockOne.Header.PrevBlock
|
||||||
|
merkleHash := &blockOne.Header.MerkleRoot
|
||||||
|
bits := blockOne.Header.Bits
|
||||||
|
nonce := blockOne.Header.Nonce
|
||||||
|
bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "block"
|
||||||
|
msg := wire.NewMsgBlock(bh)
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgBlock: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(1000000)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same block header data back out.
|
||||||
|
if !reflect.DeepEqual(&msg.Header, bh) {
|
||||||
|
t.Errorf("NewMsgBlock: wrong block header - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.Header), spew.Sdump(bh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transactions are added properly.
|
||||||
|
tx := blockOne.Transactions[0].Copy()
|
||||||
|
msg.AddTransaction(tx)
|
||||||
|
if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) {
|
||||||
|
t.Errorf("AddTransaction: wrong transactions - got %v, want %v",
|
||||||
|
spew.Sdump(msg.Transactions),
|
||||||
|
spew.Sdump(blockOne.Transactions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transactions are properly cleared.
|
||||||
|
msg.ClearTransactions()
|
||||||
|
if len(msg.Transactions) != 0 {
|
||||||
|
t.Errorf("ClearTransactions: wrong transactions - got %v, want %v",
|
||||||
|
len(msg.Transactions), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockTxShas tests the ability to generate a slice of all transaction
|
||||||
|
// hashes from a block accurately.
|
||||||
|
func TestBlockTxShas(t *testing.T) {
|
||||||
|
// Block 1, transaction 1 hash.
|
||||||
|
hashStr := "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
|
||||||
|
wantHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wantShas := []wire.ShaHash{*wantHash}
|
||||||
|
shas, err := blockOne.TxShas()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TxShas: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(shas, wantShas) {
|
||||||
|
t.Errorf("TxShas: wrong transaction hashes - got %v, want %v",
|
||||||
|
spew.Sdump(shas), spew.Sdump(wantShas))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockSha tests the ability to generate the hash of a block accurately.
|
||||||
|
func TestBlockSha(t *testing.T) {
|
||||||
|
// Block 1 hash.
|
||||||
|
hashStr := "839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
|
||||||
|
wantHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the hash produced is expected.
|
||||||
|
blockHash, err := blockOne.BlockSha()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BlockSha: %v", err)
|
||||||
|
}
|
||||||
|
if !blockHash.IsEqual(wantHash) {
|
||||||
|
t.Errorf("BlockSha: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(blockHash), spew.Sprint(wantHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockWire tests the MsgBlock wire encode and decode for various numbers
|
||||||
|
// of transaction inputs and outputs and protocol versions.
|
||||||
|
func TestBlockWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgBlock // Message to encode
|
||||||
|
out *wire.MsgBlock // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
txLocs []wire.TxLoc // Expected transaction locations
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgBlock
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgBlock to confirm error paths work correctly.
|
||||||
|
func TestBlockWireErrors(t *testing.T) {
|
||||||
|
// Use protocol version 60002 specifically here instead of the latest
|
||||||
|
// because the test data is using bytes encoded with that protocol
|
||||||
|
// version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgBlock // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in version.
|
||||||
|
{&blockOne, blockOneBytes, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in prev block hash.
|
||||||
|
{&blockOne, blockOneBytes, pver, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in merkle root.
|
||||||
|
{&blockOne, blockOneBytes, pver, 36, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in timestamp.
|
||||||
|
{&blockOne, blockOneBytes, pver, 68, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in difficulty bits.
|
||||||
|
{&blockOne, blockOneBytes, pver, 72, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in header nonce.
|
||||||
|
{&blockOne, blockOneBytes, pver, 76, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction count.
|
||||||
|
{&blockOne, blockOneBytes, pver, 80, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transactions.
|
||||||
|
{&blockOne, blockOneBytes, pver, 81, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgBlock
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockSerialize tests MsgBlock serialize and deserialize.
|
||||||
|
func TestBlockSerialize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgBlock // Message to encode
|
||||||
|
out *wire.MsgBlock // Expected decoded message
|
||||||
|
buf []byte // Serialized data
|
||||||
|
txLocs []wire.TxLoc // Expected transaction locations
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
blockOneTxLocs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Serialize the block.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.Serialize(&buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Serialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the block.
|
||||||
|
var block wire.MsgBlock
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = block.Deserialize(rbuf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Deserialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&block, test.out) {
|
||||||
|
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&block), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the block while gathering transaction location
|
||||||
|
// information.
|
||||||
|
var txLocBlock wire.MsgBlock
|
||||||
|
br := bytes.NewBuffer(test.buf)
|
||||||
|
txLocs, err := txLocBlock.DeserializeTxLoc(br)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DeserializeTxLoc #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&txLocBlock, test.out) {
|
||||||
|
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&txLocBlock), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(txLocs, test.txLocs) {
|
||||||
|
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(txLocs), spew.Sdump(test.txLocs))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockSerializeErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgBlock to confirm error paths work correctly.
|
||||||
|
func TestBlockSerializeErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgBlock // Value to encode
|
||||||
|
buf []byte // Serialized data
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in version.
|
||||||
|
{&blockOne, blockOneBytes, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in prev block hash.
|
||||||
|
{&blockOne, blockOneBytes, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in merkle root.
|
||||||
|
{&blockOne, blockOneBytes, 36, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in timestamp.
|
||||||
|
{&blockOne, blockOneBytes, 68, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in difficulty bits.
|
||||||
|
{&blockOne, blockOneBytes, 72, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in header nonce.
|
||||||
|
{&blockOne, blockOneBytes, 76, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction count.
|
||||||
|
{&blockOne, blockOneBytes, 80, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transactions.
|
||||||
|
{&blockOne, blockOneBytes, 81, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Serialize the block.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.Serialize(w)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the block.
|
||||||
|
var block wire.MsgBlock
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = block.Deserialize(r)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var txLocBlock wire.MsgBlock
|
||||||
|
br := bytes.NewBuffer(test.buf[0:test.max])
|
||||||
|
_, err = txLocBlock.DeserializeTxLoc(br)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockOverflowErrors performs tests to ensure deserializing blocks which
|
||||||
|
// are intentionally crafted to use large values for the number of transactions
|
||||||
|
// are handled properly. This could otherwise potentially be used as an attack
|
||||||
|
// vector.
|
||||||
|
func TestBlockOverflowErrors(t *testing.T) {
|
||||||
|
// Use protocol version 70001 specifically here instead of the latest
|
||||||
|
// protocol version because the test data is using bytes encoded with
|
||||||
|
// that version.
|
||||||
|
pver := uint32(70001)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
err error // Expected error
|
||||||
|
}{
|
||||||
|
// Block that claims to have ~uint64(0) transactions.
|
||||||
|
{
|
||||||
|
[]byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, // TxnCount
|
||||||
|
}, pver, &wire.MessageError{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgBlock
|
||||||
|
r := bytes.NewReader(test.buf)
|
||||||
|
err := msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize from wire format.
|
||||||
|
r = bytes.NewReader(test.buf)
|
||||||
|
err = msg.Deserialize(r)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize with transaction location info from wire format.
|
||||||
|
br := bytes.NewBuffer(test.buf)
|
||||||
|
_, err = msg.DeserializeTxLoc(br)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockSerializeSize performs tests to ensure the serialize size for
|
||||||
|
// various blocks is accurate.
|
||||||
|
func TestBlockSerializeSize(t *testing.T) {
|
||||||
|
// Block with no transactions.
|
||||||
|
noTxBlock := wire.NewMsgBlock(&blockOne.Header)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgBlock // Block to encode
|
||||||
|
size int // Expected serialized size
|
||||||
|
}{
|
||||||
|
// Block with no transactions.
|
||||||
|
{noTxBlock, 81},
|
||||||
|
|
||||||
|
// First block in the mainnet block chain.
|
||||||
|
{&blockOne, len(blockOneBytes)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
serializedSize := test.in.SerializeSize()
|
||||||
|
if serializedSize != test.size {
|
||||||
|
t.Errorf("MsgBlock.SerializeSize: #%d got: %d, want: "+
|
||||||
|
"%d", i, serializedSize, test.size)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockOne = wire.MsgBlock{
|
||||||
|
Header: wire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}),
|
||||||
|
MerkleRoot: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST
|
||||||
|
Bits: 0x1d00ffff, // 486604799
|
||||||
|
Nonce: 0x9962e301, // 2573394689
|
||||||
|
},
|
||||||
|
Transactions: []*wire.MsgTx{
|
||||||
|
{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: wire.ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04,
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block one serialized bytes.
|
||||||
|
var blockOneBytes = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x01, // TxnCount
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x01, // Varint for number of transaction inputs
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script (coinbase)
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of transaction outputs
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte uncompressed public key
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction location information for block one transactions.
|
||||||
|
var blockOneTxLocs = []wire.TxLoc{
|
||||||
|
{TxStart: 81, TxLen: 134},
|
||||||
|
}
|
90
wire/msgfilteradd.go
Normal file
90
wire/msgfilteradd.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxFilterAddDataSize is the maximum byte size of a data
|
||||||
|
// element to add to the Bloom filter. It is equal to the
|
||||||
|
// maximum element size of a script.
|
||||||
|
MaxFilterAddDataSize = 520
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgFilterAdd implements the Message interface and represents a bitcoin
|
||||||
|
// filteradd message. It is used to add a data element to an existing Bloom
|
||||||
|
// filter.
|
||||||
|
//
|
||||||
|
// This message was not added until protocol version BIP0037Version.
|
||||||
|
type MsgFilterAdd struct {
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterAdd) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filteradd message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterAdd.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
msg.Data, err = readVarBytes(r, pver, MaxFilterAddDataSize,
|
||||||
|
"filteradd data")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterAdd) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filteradd message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterAdd.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := len(msg.Data)
|
||||||
|
if size > MaxFilterAddDataSize {
|
||||||
|
str := fmt.Sprintf("filteradd size too large for message "+
|
||||||
|
"[size %v, max %v]", size, MaxFilterAddDataSize)
|
||||||
|
return messageError("MsgFilterAdd.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarBytes(w, pver, msg.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterAdd) Command() string {
|
||||||
|
return CmdFilterAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterAdd) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return uint32(VarIntSerializeSize(MaxFilterAddDataSize)) +
|
||||||
|
MaxFilterAddDataSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgFilterAdd returns a new bitcoin filteradd message that conforms to the
|
||||||
|
// Message interface. See MsgFilterAdd for details.
|
||||||
|
func NewMsgFilterAdd(data []byte) *MsgFilterAdd {
|
||||||
|
return &MsgFilterAdd{
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
189
wire/msgfilteradd_test.go
Normal file
189
wire/msgfilteradd_test.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFilterAddLatest tests the MsgFilterAdd API against the latest protocol
|
||||||
|
// version.
|
||||||
|
func TestFilterAddLatest(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
data := []byte{0x01, 0x02}
|
||||||
|
msg := wire.NewMsgFilterAdd(data)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "filteradd"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgFilterAdd: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(523)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgFilterAdd failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
var readmsg wire.MsgFilterAdd
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgFilterAdd failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterAddCrossProtocol tests the MsgFilterAdd API when encoding with the
|
||||||
|
// latest protocol version and decoding with BIP0031Version.
|
||||||
|
func TestFilterAddCrossProtocol(t *testing.T) {
|
||||||
|
data := []byte{0x01, 0x02}
|
||||||
|
msg := wire.NewMsgFilterAdd(data)
|
||||||
|
if !bytes.Equal(msg.Data, data) {
|
||||||
|
t.Errorf("should get same data back out")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgFilterAdd failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
var readmsg wire.MsgFilterAdd
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the filteradd
|
||||||
|
// message, make sure the data didn't get encoded and decoded back out.
|
||||||
|
if bytes.Equal(msg.Data, readmsg.Data) {
|
||||||
|
t.Error("should not get same data for cross protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterAddMaxDataSize tests the MsgFilterAdd API maximum data size.
|
||||||
|
func TestFilterAddMaxDataSize(t *testing.T) {
|
||||||
|
data := bytes.Repeat([]byte{0xff}, 521)
|
||||||
|
msg := wire.NewMsgFilterAdd(data)
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgFilterAdd succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with latest protocol version.
|
||||||
|
readbuf := bytes.NewReader(data)
|
||||||
|
err = msg.BtcDecode(readbuf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterAddWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgFilterAdd to confirm error paths work correctly.
|
||||||
|
func TestFilterAddWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverNoFilterAdd := wire.BIP0037Version - 1
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
baseData := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
baseFilterAdd := wire.NewMsgFilterAdd(baseData)
|
||||||
|
baseFilterAddEncoded := append([]byte{0x04}, baseData...)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgFilterAdd // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in data size.
|
||||||
|
{
|
||||||
|
baseFilterAdd, baseFilterAddEncoded, pver, 0,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in data.
|
||||||
|
{
|
||||||
|
baseFilterAdd, baseFilterAddEncoded, pver, 1,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{
|
||||||
|
baseFilterAdd, baseFilterAddEncoded, pverNoFilterAdd, 5,
|
||||||
|
wireErr, wireErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgFilterAdd
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
wire/msgfilterclear.go
Normal file
59
wire/msgfilterclear.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgFilterClear implements the Message interface and represents a bitcoin
|
||||||
|
// filterclear message which is used to reset a Bloom filter.
|
||||||
|
//
|
||||||
|
// This message was not added until protocol version BIP0037Version and has
|
||||||
|
// no payload.
|
||||||
|
type MsgFilterClear struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterClear) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filterclear message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterClear.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterClear) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filterclear message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterClear.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterClear) Command() string {
|
||||||
|
return CmdFilterClear
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterClear) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgFilterClear returns a new bitcoin filterclear message that conforms to the Message
|
||||||
|
// interface. See MsgFilterClear for details.
|
||||||
|
func NewMsgFilterClear() *MsgFilterClear {
|
||||||
|
return &MsgFilterClear{}
|
||||||
|
}
|
197
wire/msgfilterclear_test.go
Normal file
197
wire/msgfilterclear_test.go
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFilterCLearLatest tests the MsgFilterClear API against the latest
|
||||||
|
// protocol version.
|
||||||
|
func TestFilterClearLatest(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
msg := wire.NewMsgFilterClear()
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "filterclear"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgFilterClear: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterClearCrossProtocol tests the MsgFilterClear API when encoding with
|
||||||
|
// the latest protocol version and decoding with BIP0031Version.
|
||||||
|
func TestFilterClearCrossProtocol(t *testing.T) {
|
||||||
|
msg := wire.NewMsgFilterClear()
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgFilterClear failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
var readmsg wire.MsgFilterClear
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterClear succeeded when it "+
|
||||||
|
"shouldn't have %v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterClearWire tests the MsgFilterClear wire encode and decode for
|
||||||
|
// various protocol versions.
|
||||||
|
func TestFilterClearWire(t *testing.T) {
|
||||||
|
msgFilterClear := wire.NewMsgFilterClear()
|
||||||
|
msgFilterClearEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgFilterClear // Message to encode
|
||||||
|
out *wire.MsgFilterClear // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClearEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0037Version + 1.
|
||||||
|
{
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClearEncoded,
|
||||||
|
wire.BIP0037Version + 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0037Version.
|
||||||
|
{
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClear,
|
||||||
|
msgFilterClearEncoded,
|
||||||
|
wire.BIP0037Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgFilterClear
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterClearWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgFilterClear to confirm error paths work correctly.
|
||||||
|
func TestFilterClearWireErrors(t *testing.T) {
|
||||||
|
pverNoFilterClear := wire.BIP0037Version - 1
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
baseFilterClear := wire.NewMsgFilterClear()
|
||||||
|
baseFilterClearEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgFilterClear // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{
|
||||||
|
baseFilterClear, baseFilterClearEncoded,
|
||||||
|
pverNoFilterClear, 4, wireErr, wireErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgFilterClear
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
141
wire/msgfilterload.go
Normal file
141
wire/msgfilterload.go
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BloomUpdateType specifies how the filter is updated when a match is found
|
||||||
|
type BloomUpdateType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BloomUpdateNone indicates the filter is not adjusted when a match is
|
||||||
|
// found.
|
||||||
|
BloomUpdateNone BloomUpdateType = 0
|
||||||
|
|
||||||
|
// BloomUpdateAll indicates if the filter matches any data element in a
|
||||||
|
// public key script, the outpoint is serialized and inserted into the
|
||||||
|
// filter.
|
||||||
|
BloomUpdateAll BloomUpdateType = 1
|
||||||
|
|
||||||
|
// BloomUpdateP2PubkeyOnly indicates if the filter matches a data
|
||||||
|
// element in a public key script and the script is of the standard
|
||||||
|
// pay-to-pubkey or multisig, the outpoint is serialized and inserted
|
||||||
|
// into the filter.
|
||||||
|
BloomUpdateP2PubkeyOnly BloomUpdateType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxFilterLoadHashFuncs is the maximum number of hash functions to
|
||||||
|
// load into the Bloom filter.
|
||||||
|
MaxFilterLoadHashFuncs = 50
|
||||||
|
|
||||||
|
// MaxFilterLoadFilterSize is the maximum size in bytes a filter may be.
|
||||||
|
MaxFilterLoadFilterSize = 36000
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgFilterLoad implements the Message interface and represents a bitcoin
|
||||||
|
// filterload message which is used to reset a Bloom filter.
|
||||||
|
//
|
||||||
|
// This message was not added until protocol version BIP0037Version.
|
||||||
|
type MsgFilterLoad struct {
|
||||||
|
Filter []byte
|
||||||
|
HashFuncs uint32
|
||||||
|
Tweak uint32
|
||||||
|
Flags BloomUpdateType
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterLoad) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filterload message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterLoad.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
msg.Filter, err = readVarBytes(r, pver, MaxFilterLoadFilterSize,
|
||||||
|
"filterload filter size")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElements(r, &msg.HashFuncs, &msg.Tweak, &msg.Flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.HashFuncs > MaxFilterLoadHashFuncs {
|
||||||
|
str := fmt.Sprintf("too many filter hash functions for message "+
|
||||||
|
"[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs)
|
||||||
|
return messageError("MsgFilterLoad.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterLoad) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("filterload message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgFilterLoad.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := len(msg.Filter)
|
||||||
|
if size > MaxFilterLoadFilterSize {
|
||||||
|
str := fmt.Sprintf("filterload filter size too large for message "+
|
||||||
|
"[size %v, max %v]", size, MaxFilterLoadFilterSize)
|
||||||
|
return messageError("MsgFilterLoad.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.HashFuncs > MaxFilterLoadHashFuncs {
|
||||||
|
str := fmt.Sprintf("too many filter hash functions for message "+
|
||||||
|
"[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs)
|
||||||
|
return messageError("MsgFilterLoad.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarBytes(w, pver, msg.Filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElements(w, msg.HashFuncs, msg.Tweak, msg.Flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterLoad) Command() string {
|
||||||
|
return CmdFilterLoad
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgFilterLoad) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num filter bytes (varInt) + filter + 4 bytes hash funcs +
|
||||||
|
// 4 bytes tweak + 1 byte flags.
|
||||||
|
return uint32(VarIntSerializeSize(MaxFilterLoadFilterSize)) +
|
||||||
|
MaxFilterLoadFilterSize + 9
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgFilterLoad returns a new bitcoin filterload message that conforms to
|
||||||
|
// the Message interface. See MsgFilterLoad for details.
|
||||||
|
func NewMsgFilterLoad(filter []byte, hashFuncs uint32, tweak uint32, flags BloomUpdateType) *MsgFilterLoad {
|
||||||
|
return &MsgFilterLoad{
|
||||||
|
Filter: filter,
|
||||||
|
HashFuncs: hashFuncs,
|
||||||
|
Tweak: tweak,
|
||||||
|
Flags: flags,
|
||||||
|
}
|
||||||
|
}
|
230
wire/msgfilterload_test.go
Normal file
230
wire/msgfilterload_test.go
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright (c) 2014 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestFilterCLearLatest tests the MsgFilterLoad API against the latest protocol
|
||||||
|
// version.
|
||||||
|
func TestFilterLoadLatest(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
data := []byte{0x01, 0x02}
|
||||||
|
msg := wire.NewMsgFilterLoad(data, 10, 0, 0)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "filterload"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgFilterLoad: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(36012)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayLoadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgFilterLoad failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := wire.MsgFilterLoad{}
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgFilterLoad failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterLoadCrossProtocol tests the MsgFilterLoad API when encoding with
|
||||||
|
// the latest protocol version and decoding with BIP0031Version.
|
||||||
|
func TestFilterLoadCrossProtocol(t *testing.T) {
|
||||||
|
data := []byte{0x01, 0x02}
|
||||||
|
msg := wire.NewMsgFilterLoad(data, 10, 0, 0)
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of NewMsgFilterLoad failed %v err <%v>", msg,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
var readmsg wire.MsgFilterLoad
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterLoadMaxFilterSize tests the MsgFilterLoad API maximum filter size.
|
||||||
|
func TestFilterLoadMaxFilterSize(t *testing.T) {
|
||||||
|
data := bytes.Repeat([]byte{0xff}, 36001)
|
||||||
|
msg := wire.NewMsgFilterLoad(data, 10, 0, 0)
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with latest protocol version.
|
||||||
|
readbuf := bytes.NewReader(data)
|
||||||
|
err = msg.BtcDecode(readbuf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterLoadMaxHashFuncsSize tests the MsgFilterLoad API maximum hash functions.
|
||||||
|
func TestFilterLoadMaxHashFuncsSize(t *testing.T) {
|
||||||
|
data := bytes.Repeat([]byte{0xff}, 10)
|
||||||
|
msg := wire.NewMsgFilterLoad(data, 61, 0, 0)
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBuf := []byte{
|
||||||
|
0x0a, // filter size
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // filter
|
||||||
|
0x3d, 0x00, 0x00, 0x00, // max hash funcs
|
||||||
|
0x00, 0x00, 0x00, 0x00, // tweak
|
||||||
|
0x00, // update Type
|
||||||
|
}
|
||||||
|
// Decode with latest protocol version.
|
||||||
|
readbuf := bytes.NewReader(newBuf)
|
||||||
|
err = msg.BtcDecode(readbuf, wire.ProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFilterLoadWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgFilterLoad to confirm error paths work correctly.
|
||||||
|
func TestFilterLoadWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverNoFilterLoad := wire.BIP0037Version - 1
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
baseFilter := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
baseFilterLoad := wire.NewMsgFilterLoad(baseFilter, 10, 0,
|
||||||
|
wire.BloomUpdateNone)
|
||||||
|
baseFilterLoadEncoded := append([]byte{0x04}, baseFilter...)
|
||||||
|
baseFilterLoadEncoded = append(baseFilterLoadEncoded,
|
||||||
|
0x00, 0x00, 0x00, 0x0a, // HashFuncs
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Tweak
|
||||||
|
0x00) // Flags
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgFilterLoad // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in filter size.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pver, 0,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in filter.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pver, 1,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in hash funcs.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pver, 5,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in tweak.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pver, 9,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in flags.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pver, 13,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{
|
||||||
|
baseFilterLoad, baseFilterLoadEncoded, pverNoFilterLoad,
|
||||||
|
10, wireErr, wireErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgFilterLoad
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
47
wire/msggetaddr.go
Normal file
47
wire/msggetaddr.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetAddr implements the Message interface and represents a bitcoin
|
||||||
|
// getaddr message. It is used to request a list of known active peers on the
|
||||||
|
// network from a peer to help identify potential nodes. The list is returned
|
||||||
|
// via one or more addr messages (MsgAddr).
|
||||||
|
//
|
||||||
|
// This message has no payload.
|
||||||
|
type MsgGetAddr struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) Command() string {
|
||||||
|
return CmdGetAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetAddr returns a new bitcoin getaddr message that conforms to the
|
||||||
|
// Message interface. See MsgGetAddr for details.
|
||||||
|
func NewMsgGetAddr() *MsgGetAddr {
|
||||||
|
return &MsgGetAddr{}
|
||||||
|
}
|
123
wire/msggetaddr_test.go
Normal file
123
wire/msggetaddr_test.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetAddr tests the MsgGetAddr API.
|
||||||
|
func TestGetAddr(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getaddr"
|
||||||
|
msg := wire.NewMsgGetAddr()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetAddrWire tests the MsgGetAddr wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestGetAddrWire(t *testing.T) {
|
||||||
|
msgGetAddr := wire.NewMsgGetAddr()
|
||||||
|
msgGetAddrEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetAddr // Message to encode
|
||||||
|
out *wire.MsgGetAddr // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgGetAddr
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
144
wire/msggetblocks.go
Normal file
144
wire/msggetblocks.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
||||||
|
// per message.
|
||||||
|
const MaxBlockLocatorsPerMsg = 500
|
||||||
|
|
||||||
|
// MsgGetBlocks implements the Message interface and represents a bitcoin
|
||||||
|
// getblocks message. It is used to request a list of blocks starting after the
|
||||||
|
// last known hash in the slice of block locator hashes. The list is returned
|
||||||
|
// via an inv message (MsgInv) and is limited by a specific hash to stop at or
|
||||||
|
// the maximum number of blocks per message, which is currently 500.
|
||||||
|
//
|
||||||
|
// Set the HashStop field to the hash at which to stop and use
|
||||||
|
// AddBlockLocatorHash to build up the list of block locator hashes.
|
||||||
|
//
|
||||||
|
// The algorithm for building the block locator hashes should be to add the
|
||||||
|
// hashes in reverse order until you reach the genesis block. In order to keep
|
||||||
|
// the list of locator hashes to a reasonable number of entries, first add the
|
||||||
|
// most recent 10 block hashes, then double the step each loop iteration to
|
||||||
|
// exponentially decrease the number of hashes the further away from head and
|
||||||
|
// closer to the genesis block you get.
|
||||||
|
type MsgGetBlocks struct {
|
||||||
|
ProtocolVersion uint32
|
||||||
|
BlockLocatorHashes []*ShaHash
|
||||||
|
HashStop ShaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockLocatorHash adds a new block locator hash to the message.
|
||||||
|
func (msg *MsgGetBlocks) AddBlockLocatorHash(hash *ShaHash) error {
|
||||||
|
if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message [max %v]",
|
||||||
|
MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetBlocks.AddBlockLocatorHash", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readElement(r, &msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num block locator hashes and limit to max.
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetBlocks.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = make([]*ShaHash, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
sha := ShaHash{}
|
||||||
|
err := readElement(r, &sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(&sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
count := len(msg.BlockLocatorHashes)
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetBlocks.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hash := range msg.BlockLocatorHashes {
|
||||||
|
err = writeElement(w, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) Command() string {
|
||||||
|
return CmdGetBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetBlocks returns a new bitcoin getblocks message that conforms to the
|
||||||
|
// Message interface using the passed parameters and defaults for the remaining
|
||||||
|
// fields.
|
||||||
|
func NewMsgGetBlocks(hashStop *ShaHash) *MsgGetBlocks {
|
||||||
|
return &MsgGetBlocks{
|
||||||
|
ProtocolVersion: ProtocolVersion,
|
||||||
|
BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg),
|
||||||
|
HashStop: *hashStop,
|
||||||
|
}
|
||||||
|
}
|
390
wire/msggetblocks_test.go
Normal file
390
wire/msggetblocks_test.go
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetBlocks tests the MsgGetBlocks API.
|
||||||
|
func TestGetBlocks(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
locatorHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same data back out.
|
||||||
|
msg := wire.NewMsgGetBlocks(hashStop)
|
||||||
|
if !msg.HashStop.IsEqual(hashStop) {
|
||||||
|
t.Errorf("NewMsgGetBlocks: wrong stop hash - got %v, want %v",
|
||||||
|
msg.HashStop, hashStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getblocks"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetBlocks: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
wantPayload := uint32(16045)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure block locator hashes are added properly.
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: %v", err)
|
||||||
|
}
|
||||||
|
if msg.BlockLocatorHashes[0] != locatorHash {
|
||||||
|
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
||||||
|
"got %v, want %v",
|
||||||
|
spew.Sprint(msg.BlockLocatorHashes[0]),
|
||||||
|
spew.Sprint(locatorHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed block locator hashes per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: expected error on too many " +
|
||||||
|
"block locator hashes not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetBlocksWire tests the MsgGetBlocks wire encode and decode for various
|
||||||
|
// numbers of block locator hashes and protocol versions.
|
||||||
|
func TestGetBlocksWire(t *testing.T) {
|
||||||
|
// Set protocol inside getblocks message.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetBlocks message with no block locators or stop hash.
|
||||||
|
noLocators := wire.NewMsgGetBlocks(&wire.ShaHash{})
|
||||||
|
noLocators.ProtocolVersion = pver
|
||||||
|
noLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x00, // Varint for number of block locator hashes
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetBlocks message with multiple block locators and a stop hash.
|
||||||
|
multiLocators := wire.NewMsgGetBlocks(hashStop)
|
||||||
|
multiLocators.AddBlockLocatorHash(hashLocator2)
|
||||||
|
multiLocators.AddBlockLocatorHash(hashLocator)
|
||||||
|
multiLocators.ProtocolVersion = pver
|
||||||
|
multiLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetBlocks // Message to encode
|
||||||
|
out *wire.MsgGetBlocks // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Versionwith multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgGetBlocks
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetBlocksWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgGetBlocks to confirm error paths work correctly.
|
||||||
|
func TestGetBlocksWireErrors(t *testing.T) {
|
||||||
|
// Set protocol inside getheaders message. Use protocol version 60002
|
||||||
|
// specifically here instead of the latest because the test data is
|
||||||
|
// using bytes encoded with that protocol version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetBlocks message with multiple block locators and a stop hash.
|
||||||
|
baseGetBlocks := wire.NewMsgGetBlocks(hashStop)
|
||||||
|
baseGetBlocks.ProtocolVersion = pver
|
||||||
|
baseGetBlocks.AddBlockLocatorHash(hashLocator2)
|
||||||
|
baseGetBlocks.AddBlockLocatorHash(hashLocator)
|
||||||
|
baseGetBlocksEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed
|
||||||
|
// block locator hashes.
|
||||||
|
maxGetBlocks := wire.NewMsgGetBlocks(hashStop)
|
||||||
|
for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
maxGetBlocks.AddBlockLocatorHash(&mainNetGenesisHash)
|
||||||
|
}
|
||||||
|
maxGetBlocks.BlockLocatorHashes = append(maxGetBlocks.BlockLocatorHashes,
|
||||||
|
&mainNetGenesisHash)
|
||||||
|
maxGetBlocksEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetBlocks // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in protocol version.
|
||||||
|
{baseGetBlocks, baseGetBlocksEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in block locator hash count.
|
||||||
|
{baseGetBlocks, baseGetBlocksEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in block locator hashes.
|
||||||
|
{baseGetBlocks, baseGetBlocksEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in stop hash.
|
||||||
|
{baseGetBlocks, baseGetBlocksEncoded, pver, 69, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max block locator hashes.
|
||||||
|
{maxGetBlocks, maxGetBlocksEncoded, pver, 7, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgGetBlocks
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
wire/msggetdata.go
Normal file
130
wire/msggetdata.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetData implements the Message interface and represents a bitcoin
|
||||||
|
// getdata message. It is used to request data such as blocks and transactions
|
||||||
|
// from another peer. It should be used in response to the inv (MsgInv) message
|
||||||
|
// to request the actual data referenced by each inventory vector the receiving
|
||||||
|
// peer doesn't already have. Each message is limited to a maximum number of
|
||||||
|
// inventory vectors, which is currently 50,000. As a result, multiple messages
|
||||||
|
// must be used to request larger amounts of data.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending a getdata message to another peer.
|
||||||
|
type MsgGetData struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgGetData) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [max %v]",
|
||||||
|
MaxInvPerMsg)
|
||||||
|
return messageError("MsgGetData.AddInvVect", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgGetData.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = make([]*InvVect, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgGetData.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) Command() string {
|
||||||
|
return CmdGetData
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetData returns a new bitcoin getdata message that conforms to the
|
||||||
|
// Message interface. See MsgGetData for details.
|
||||||
|
func NewMsgGetData() *MsgGetData {
|
||||||
|
return &MsgGetData{
|
||||||
|
InvList: make([]*InvVect, 0, defaultInvListAlloc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetDataSizeHint returns a new bitcoin getdata message that conforms to
|
||||||
|
// the Message interface. See MsgGetData for details. This function differs
|
||||||
|
// from NewMsgGetData in that it allows a default allocation size for the
|
||||||
|
// backing array which houses the inventory vector list. This allows callers
|
||||||
|
// who know in advance how large the inventory list will grow to avoid the
|
||||||
|
// overhead of growing the internal backing array several times when appending
|
||||||
|
// large amounts of inventory vectors with AddInvVect. Note that the specified
|
||||||
|
// hint is just that - a hint that is used for the default allocation size.
|
||||||
|
// Adding more (or less) inventory vectors will still work properly. The size
|
||||||
|
// hint is limited to MaxInvPerMsg.
|
||||||
|
func NewMsgGetDataSizeHint(sizeHint uint) *MsgGetData {
|
||||||
|
// Limit the specified hint to the maximum allow per message.
|
||||||
|
if sizeHint > MaxInvPerMsg {
|
||||||
|
sizeHint = MaxInvPerMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MsgGetData{
|
||||||
|
InvList: make([]*InvVect, 0, sizeHint),
|
||||||
|
}
|
||||||
|
}
|
331
wire/msggetdata_test.go
Normal file
331
wire/msggetdata_test.go
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetData tests the MsgGetData API.
|
||||||
|
func TestGetData(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getdata"
|
||||||
|
msg := wire.NewMsgGetData()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetData: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := wire.ShaHash{}
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating the message with a size hint larger than the max
|
||||||
|
// works as expected.
|
||||||
|
msg = wire.NewMsgGetDataSizeHint(wire.MaxInvPerMsg + 1)
|
||||||
|
wantCap := wire.MaxInvPerMsg
|
||||||
|
if cap(msg.InvList) != wantCap {
|
||||||
|
t.Errorf("NewMsgGetDataSizeHint: wrong cap for size hint - "+
|
||||||
|
"got %v, want %v", cap(msg.InvList), wantCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetDataWire tests the MsgGetData wire encode and decode for various
|
||||||
|
// numbers of inventory vectors and protocol versions.
|
||||||
|
func TestGetDataWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
iv2 := wire.NewInvVect(wire.InvTypeTx, txHash)
|
||||||
|
|
||||||
|
// Empty MsgGetData message.
|
||||||
|
NoInv := wire.NewMsgGetData()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetData message with multiple inventory vectors.
|
||||||
|
MultiInv := wire.NewMsgGetData()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvTypeTx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetData // Message to encode
|
||||||
|
out *wire.MsgGetData // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgGetData
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetDataWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgGetData to confirm error paths work correctly.
|
||||||
|
func TestGetDataWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
|
||||||
|
// Base message used to induce errors.
|
||||||
|
baseGetData := wire.NewMsgGetData()
|
||||||
|
baseGetData.AddInvVect(iv)
|
||||||
|
baseGetDataEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed inv
|
||||||
|
// vectors.
|
||||||
|
maxGetData := wire.NewMsgGetData()
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
maxGetData.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
maxGetData.InvList = append(maxGetData.InvList, iv)
|
||||||
|
maxGetDataEncoded := []byte{
|
||||||
|
0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetData // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in inventory vector count
|
||||||
|
{baseGetData, baseGetDataEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in inventory list.
|
||||||
|
{baseGetData, baseGetDataEncoded, pver, 1, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max inventory vectors.
|
||||||
|
{maxGetData, maxGetDataEncoded, pver, 3, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgGetData
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
139
wire/msggetheaders.go
Normal file
139
wire/msggetheaders.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetHeaders implements the Message interface and represents a bitcoin
|
||||||
|
// getheaders message. It is used to request a list of block headers for
|
||||||
|
// blocks starting after the last known hash in the slice of block locator
|
||||||
|
// hashes. The list is returned via a headers message (MsgHeaders) and is
|
||||||
|
// limited by a specific hash to stop at or the maximum number of block headers
|
||||||
|
// per message, which is currently 2000.
|
||||||
|
//
|
||||||
|
// Set the HashStop field to the hash at which to stop and use
|
||||||
|
// AddBlockLocatorHash to build up the list of block locator hashes.
|
||||||
|
//
|
||||||
|
// The algorithm for building the block locator hashes should be to add the
|
||||||
|
// hashes in reverse order until you reach the genesis block. In order to keep
|
||||||
|
// the list of locator hashes to a resonable number of entries, first add the
|
||||||
|
// most recent 10 block hashes, then double the step each loop iteration to
|
||||||
|
// exponentially decrease the number of hashes the further away from head and
|
||||||
|
// closer to the genesis block you get.
|
||||||
|
type MsgGetHeaders struct {
|
||||||
|
ProtocolVersion uint32
|
||||||
|
BlockLocatorHashes []*ShaHash
|
||||||
|
HashStop ShaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockLocatorHash adds a new block locator hash to the message.
|
||||||
|
func (msg *MsgGetHeaders) AddBlockLocatorHash(hash *ShaHash) error {
|
||||||
|
if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message [max %v]",
|
||||||
|
MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetHeaders.AddBlockLocatorHash", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readElement(r, &msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num block locator hashes and limit to max.
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetHeaders.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = make([]*ShaHash, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
sha := ShaHash{}
|
||||||
|
err := readElement(r, &sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(&sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max block locator hashes per message.
|
||||||
|
count := len(msg.BlockLocatorHashes)
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block locator hashes for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockLocatorsPerMsg)
|
||||||
|
return messageError("MsgGetHeaders.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sha := range msg.BlockLocatorHashes {
|
||||||
|
err := writeElement(w, sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) Command() string {
|
||||||
|
return CmdGetHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Version 4 bytes + num block locator hashes (varInt) + max allowed block
|
||||||
|
// locators + hash stop.
|
||||||
|
return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetHeaders returns a new bitcoin getheaders message that conforms to
|
||||||
|
// the Message interface. See MsgGetHeaders for details.
|
||||||
|
func NewMsgGetHeaders() *MsgGetHeaders {
|
||||||
|
return &MsgGetHeaders{
|
||||||
|
BlockLocatorHashes: make([]*ShaHash, 0, MaxBlockLocatorsPerMsg),
|
||||||
|
}
|
||||||
|
}
|
381
wire/msggetheaders_test.go
Normal file
381
wire/msggetheaders_test.go
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetHeaders tests the MsgGetHeader API.
|
||||||
|
func TestGetHeaders(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
locatorHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getheaders"
|
||||||
|
msg := wire.NewMsgGetHeaders()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetHeaders: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
wantPayload := uint32(16045)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure block locator hashes are added properly.
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: %v", err)
|
||||||
|
}
|
||||||
|
if msg.BlockLocatorHashes[0] != locatorHash {
|
||||||
|
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
||||||
|
"got %v, want %v",
|
||||||
|
spew.Sprint(msg.BlockLocatorHashes[0]),
|
||||||
|
spew.Sprint(locatorHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed block locator hashes per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: expected error on too many " +
|
||||||
|
"block locator hashes not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetHeadersWire tests the MsgGetHeaders wire encode and decode for various
|
||||||
|
// numbers of block locator hashes and protocol versions.
|
||||||
|
func TestGetHeadersWire(t *testing.T) {
|
||||||
|
// Set protocol inside getheaders message. Use protocol version 60002
|
||||||
|
// specifically here instead of the latest because the test data is
|
||||||
|
// using bytes encoded with that protocol version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetHeaders message with no block locators or stop hash.
|
||||||
|
noLocators := wire.NewMsgGetHeaders()
|
||||||
|
noLocators.ProtocolVersion = pver
|
||||||
|
noLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x00, // Varint for number of block locator hashes
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetHeaders message with multiple block locators and a stop hash.
|
||||||
|
multiLocators := wire.NewMsgGetHeaders()
|
||||||
|
multiLocators.ProtocolVersion = pver
|
||||||
|
multiLocators.HashStop = *hashStop
|
||||||
|
multiLocators.AddBlockLocatorHash(hashLocator2)
|
||||||
|
multiLocators.AddBlockLocatorHash(hashLocator)
|
||||||
|
multiLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetHeaders // Message to encode
|
||||||
|
out *wire.MsgGetHeaders // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Versionwith multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no block locators.
|
||||||
|
{
|
||||||
|
noLocators,
|
||||||
|
noLocators,
|
||||||
|
noLocatorsEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion multiple block locators.
|
||||||
|
{
|
||||||
|
multiLocators,
|
||||||
|
multiLocators,
|
||||||
|
multiLocatorsEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgGetHeaders
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetHeadersWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgGetHeaders to confirm error paths work correctly.
|
||||||
|
func TestGetHeadersWireErrors(t *testing.T) {
|
||||||
|
// Set protocol inside getheaders message. Use protocol version 60002
|
||||||
|
// specifically here instead of the latest because the test data is
|
||||||
|
// using bytes encoded with that protocol version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetHeaders message with multiple block locators and a stop hash.
|
||||||
|
baseGetHeaders := wire.NewMsgGetHeaders()
|
||||||
|
baseGetHeaders.ProtocolVersion = pver
|
||||||
|
baseGetHeaders.HashStop = *hashStop
|
||||||
|
baseGetHeaders.AddBlockLocatorHash(hashLocator2)
|
||||||
|
baseGetHeaders.AddBlockLocatorHash(hashLocator)
|
||||||
|
baseGetHeadersEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed
|
||||||
|
// block locator hashes.
|
||||||
|
maxGetHeaders := wire.NewMsgGetHeaders()
|
||||||
|
for i := 0; i < wire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
maxGetHeaders.AddBlockLocatorHash(&mainNetGenesisHash)
|
||||||
|
}
|
||||||
|
maxGetHeaders.BlockLocatorHashes = append(maxGetHeaders.BlockLocatorHashes,
|
||||||
|
&mainNetGenesisHash)
|
||||||
|
maxGetHeadersEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0xfd, 0xf5, 0x01, // Varint for number of block loc hashes (501)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgGetHeaders // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in protocol version.
|
||||||
|
{baseGetHeaders, baseGetHeadersEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in block locator hash count.
|
||||||
|
{baseGetHeaders, baseGetHeadersEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in block locator hashes.
|
||||||
|
{baseGetHeaders, baseGetHeadersEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in stop hash.
|
||||||
|
{baseGetHeaders, baseGetHeadersEncoded, pver, 69, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max block locator hashes.
|
||||||
|
{maxGetHeaders, maxGetHeadersEncoded, pver, 7, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgGetHeaders
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
wire/msgheaders.go
Normal file
134
wire/msgheaders.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxBlockHeadersPerMsg is the maximum number of block headers that can be in
|
||||||
|
// a single bitcoin headers message.
|
||||||
|
const MaxBlockHeadersPerMsg = 2000
|
||||||
|
|
||||||
|
// MsgHeaders implements the Message interface and represents a bitcoin headers
|
||||||
|
// message. It is used to deliver block header information in response
|
||||||
|
// to a getheaders message (MsgGetHeaders). The maximum number of block headers
|
||||||
|
// per message is currently 2000. See MsgGetHeaders for details on requesting
|
||||||
|
// the headers.
|
||||||
|
type MsgHeaders struct {
|
||||||
|
Headers []*BlockHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockHeader adds a new block header to the message.
|
||||||
|
func (msg *MsgHeaders) AddBlockHeader(bh *BlockHeader) error {
|
||||||
|
if len(msg.Headers)+1 > MaxBlockHeadersPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block headers in message [max %v]",
|
||||||
|
MaxBlockHeadersPerMsg)
|
||||||
|
return messageError("MsgHeaders.AddBlockHeader", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = append(msg.Headers, bh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max block headers per message.
|
||||||
|
if count > MaxBlockHeadersPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block headers for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockHeadersPerMsg)
|
||||||
|
return messageError("MsgHeaders.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = make([]*BlockHeader, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
bh := BlockHeader{}
|
||||||
|
err := readBlockHeader(r, pver, &bh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
txCount, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the transaction count is zero for headers.
|
||||||
|
if txCount > 0 {
|
||||||
|
str := fmt.Sprintf("block headers may not contain "+
|
||||||
|
"transactions [count %v]", txCount)
|
||||||
|
return messageError("MsgHeaders.BtcDecode", str)
|
||||||
|
}
|
||||||
|
msg.AddBlockHeader(&bh)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max block headers per message.
|
||||||
|
count := len(msg.Headers)
|
||||||
|
if count > MaxBlockHeadersPerMsg {
|
||||||
|
str := fmt.Sprintf("too many block headers for message "+
|
||||||
|
"[count %v, max %v]", count, MaxBlockHeadersPerMsg)
|
||||||
|
return messageError("MsgHeaders.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bh := range msg.Headers {
|
||||||
|
err := writeBlockHeader(w, pver, bh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The wire protocol encoding always includes a 0 for the number
|
||||||
|
// of transactions on header messages. This is really just an
|
||||||
|
// artifact of the way the original implementation serializes
|
||||||
|
// block headers, but it is required.
|
||||||
|
err = writeVarInt(w, pver, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) Command() string {
|
||||||
|
return CmdHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num headers (varInt) + max allowed headers (header length + 1 byte
|
||||||
|
// for the number of transactions which is always 0).
|
||||||
|
return MaxVarIntPayload + ((MaxBlockHeaderPayload + 1) *
|
||||||
|
MaxBlockHeadersPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgHeaders returns a new bitcoin headers message that conforms to the
|
||||||
|
// Message interface. See MsgHeaders for details.
|
||||||
|
func NewMsgHeaders() *MsgHeaders {
|
||||||
|
return &MsgHeaders{
|
||||||
|
Headers: make([]*BlockHeader, 0, MaxBlockHeadersPerMsg),
|
||||||
|
}
|
||||||
|
}
|
350
wire/msgheaders_test.go
Normal file
350
wire/msgheaders_test.go
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHeaders tests the MsgHeaders API.
|
||||||
|
func TestHeaders(t *testing.T) {
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "headers"
|
||||||
|
msg := wire.NewMsgHeaders()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgHeaders: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num headers (varInt) + max allowed headers (header length + 1 byte
|
||||||
|
// for the number of transactions which is always 0).
|
||||||
|
wantPayload := uint32(162009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure headers are added properly.
|
||||||
|
bh := &blockOne.Header
|
||||||
|
msg.AddBlockHeader(bh)
|
||||||
|
if !reflect.DeepEqual(msg.Headers[0], bh) {
|
||||||
|
t.Errorf("AddHeader: wrong header - got %v, want %v",
|
||||||
|
spew.Sdump(msg.Headers),
|
||||||
|
spew.Sdump(bh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed headers per message returns
|
||||||
|
// error.
|
||||||
|
var err error
|
||||||
|
for i := 0; i < wire.MaxBlockHeadersPerMsg+1; i++ {
|
||||||
|
err = msg.AddBlockHeader(bh)
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(&wire.MessageError{}) {
|
||||||
|
t.Errorf("AddBlockHeader: expected error on too many headers " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHeadersWire tests the MsgHeaders wire encode and decode for various
|
||||||
|
// numbers of headers and protocol versions.
|
||||||
|
func TestHeadersWire(t *testing.T) {
|
||||||
|
hash := mainNetGenesisHash
|
||||||
|
merkleHash := blockOne.Header.MerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
nonce := uint32(0x9962e301)
|
||||||
|
bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
bh.Version = blockOne.Header.Version
|
||||||
|
bh.Timestamp = blockOne.Header.Timestamp
|
||||||
|
|
||||||
|
// Empty headers message.
|
||||||
|
noHeaders := wire.NewMsgHeaders()
|
||||||
|
noHeadersEncoded := []byte{
|
||||||
|
0x00, // Varint for number of headers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers message with one header.
|
||||||
|
oneHeader := wire.NewMsgHeaders()
|
||||||
|
oneHeader.AddBlockHeader(bh)
|
||||||
|
oneHeaderEncoded := []byte{
|
||||||
|
0x01, // VarInt for number of headers.
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x00, // TxnCount (0 for headers message)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgHeaders // Message to encode
|
||||||
|
out *wire.MsgHeaders // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
// Protocol version NetAddressTimeVersion with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgHeaders
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHeadersWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgHeaders to confirm error paths work correctly.
|
||||||
|
func TestHeadersWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
hash := mainNetGenesisHash
|
||||||
|
merkleHash := blockOne.Header.MerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
nonce := uint32(0x9962e301)
|
||||||
|
bh := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
bh.Version = blockOne.Header.Version
|
||||||
|
bh.Timestamp = blockOne.Header.Timestamp
|
||||||
|
|
||||||
|
// Headers message with one header.
|
||||||
|
oneHeader := wire.NewMsgHeaders()
|
||||||
|
oneHeader.AddBlockHeader(bh)
|
||||||
|
oneHeaderEncoded := []byte{
|
||||||
|
0x01, // VarInt for number of headers.
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x00, // TxnCount (0 for headers message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed
|
||||||
|
// headers.
|
||||||
|
maxHeaders := wire.NewMsgHeaders()
|
||||||
|
for i := 0; i < wire.MaxBlockHeadersPerMsg; i++ {
|
||||||
|
maxHeaders.AddBlockHeader(bh)
|
||||||
|
}
|
||||||
|
maxHeaders.Headers = append(maxHeaders.Headers, bh)
|
||||||
|
maxHeadersEncoded := []byte{
|
||||||
|
0xfd, 0xd1, 0x07, // Varint for number of addresses (2001)7D1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intentionally invalid block header that has a transaction count used
|
||||||
|
// to force errors.
|
||||||
|
bhTrans := wire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
bhTrans.Version = blockOne.Header.Version
|
||||||
|
bhTrans.Timestamp = blockOne.Header.Timestamp
|
||||||
|
|
||||||
|
transHeader := wire.NewMsgHeaders()
|
||||||
|
transHeader.AddBlockHeader(bhTrans)
|
||||||
|
transHeaderEncoded := []byte{
|
||||||
|
0x01, // VarInt for number of headers.
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x01, // TxnCount (should be 0 for headers message, but 1 to force error)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgHeaders // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in header count.
|
||||||
|
{oneHeader, oneHeaderEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in block header.
|
||||||
|
{oneHeader, oneHeaderEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max headers.
|
||||||
|
{maxHeaders, maxHeadersEncoded, pver, 3, wireErr, wireErr},
|
||||||
|
// Force error with number of transactions.
|
||||||
|
{transHeader, transHeaderEncoded, pver, 81, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with included transactions.
|
||||||
|
{transHeader, transHeaderEncoded, pver, len(transHeaderEncoded), nil, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgHeaders
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
138
wire/msginv.go
Normal file
138
wire/msginv.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultInvListAlloc is the default size used for the backing array for an
|
||||||
|
// inventory list. The array will dynamically grow as needed, but this
|
||||||
|
// figure is intended to provide enough space for the max number of inventory
|
||||||
|
// vectors in a *typical* inventory message without needing to grow the backing
|
||||||
|
// array multiple times. Technically, the list can grow to MaxInvPerMsg, but
|
||||||
|
// rather than using that large figure, this figure more accurately reflects the
|
||||||
|
// typical case.
|
||||||
|
const defaultInvListAlloc = 1000
|
||||||
|
|
||||||
|
// MsgInv implements the Message interface and represents a bitcoin inv message.
|
||||||
|
// It is used to advertise a peer's known data such as blocks and transactions
|
||||||
|
// through inventory vectors. It may be sent unsolicited to inform other peers
|
||||||
|
// of the data or in response to a getblocks message (MsgGetBlocks). Each
|
||||||
|
// message is limited to a maximum number of inventory vectors, which is
|
||||||
|
// currently 50,000.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending an inv message to another peer.
|
||||||
|
type MsgInv struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgInv) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [max %v]",
|
||||||
|
MaxInvPerMsg)
|
||||||
|
return messageError("MsgInv.AddInvVect", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgInv.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = make([]*InvVect, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgInv.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) Command() string {
|
||||||
|
return CmdInv
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgInv returns a new bitcoin inv message that conforms to the Message
|
||||||
|
// interface. See MsgInv for details.
|
||||||
|
func NewMsgInv() *MsgInv {
|
||||||
|
return &MsgInv{
|
||||||
|
InvList: make([]*InvVect, 0, defaultInvListAlloc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgInvSizeHint returns a new bitcoin inv message that conforms to the
|
||||||
|
// Message interface. See MsgInv for details. This function differs from
|
||||||
|
// NewMsgInv in that it allows a default allocation size for the backing array
|
||||||
|
// which houses the inventory vector list. This allows callers who know in
|
||||||
|
// advance how large the inventory list will grow to avoid the overhead of
|
||||||
|
// growing the internal backing array several times when appending large amounts
|
||||||
|
// of inventory vectors with AddInvVect. Note that the specified hint is just
|
||||||
|
// that - a hint that is used for the default allocation size. Adding more
|
||||||
|
// (or less) inventory vectors will still work properly. The size hint is
|
||||||
|
// limited to MaxInvPerMsg.
|
||||||
|
func NewMsgInvSizeHint(sizeHint uint) *MsgInv {
|
||||||
|
// Limit the specified hint to the maximum allow per message.
|
||||||
|
if sizeHint > MaxInvPerMsg {
|
||||||
|
sizeHint = MaxInvPerMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MsgInv{
|
||||||
|
InvList: make([]*InvVect, 0, sizeHint),
|
||||||
|
}
|
||||||
|
}
|
332
wire/msginv_test.go
Normal file
332
wire/msginv_test.go
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInv tests the MsgInv API.
|
||||||
|
func TestInv(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "inv"
|
||||||
|
msg := wire.NewMsgInv()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgInv: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := wire.ShaHash{}
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure creating the message with a size hint larger than the max
|
||||||
|
// works as expected.
|
||||||
|
msg = wire.NewMsgInvSizeHint(wire.MaxInvPerMsg + 1)
|
||||||
|
wantCap := wire.MaxInvPerMsg
|
||||||
|
if cap(msg.InvList) != wantCap {
|
||||||
|
t.Errorf("NewMsgInvSizeHint: wrong cap for size hint - "+
|
||||||
|
"got %v, want %v", cap(msg.InvList), wantCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvWire tests the MsgInv wire encode and decode for various numbers
|
||||||
|
// of inventory vectors and protocol versions.
|
||||||
|
func TestInvWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
iv2 := wire.NewInvVect(wire.InvTypeTx, txHash)
|
||||||
|
|
||||||
|
// Empty inv message.
|
||||||
|
NoInv := wire.NewMsgInv()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inv message with multiple inventory vectors.
|
||||||
|
MultiInv := wire.NewMsgInv()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvTypeTx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgInv // Message to encode
|
||||||
|
out *wire.MsgInv // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgInv
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgInv to confirm error paths work correctly.
|
||||||
|
func TestInvWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
|
||||||
|
// Base inv message used to induce errors.
|
||||||
|
baseInv := wire.NewMsgInv()
|
||||||
|
baseInv.AddInvVect(iv)
|
||||||
|
baseInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inv message that forces an error by having more than the max allowed
|
||||||
|
// inv vectors.
|
||||||
|
maxInv := wire.NewMsgInv()
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
maxInv.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
maxInv.InvList = append(maxInv.InvList, iv)
|
||||||
|
maxInvEncoded := []byte{
|
||||||
|
0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgInv // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in inventory vector count
|
||||||
|
{baseInv, baseInvEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in inventory list.
|
||||||
|
{baseInv, baseInvEncoded, pver, 1, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max inventory vectors.
|
||||||
|
{maxInv, maxInvEncoded, pver, 3, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgInv
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
60
wire/msgmempool.go
Normal file
60
wire/msgmempool.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgMemPool implements the Message interface and represents a bitcoin mempool
|
||||||
|
// message. It is used to request a list of transactions still in the active
|
||||||
|
// memory pool of a relay.
|
||||||
|
//
|
||||||
|
// This message has no payload and was not added until protocol versions
|
||||||
|
// starting with BIP0035Version.
|
||||||
|
type MsgMemPool struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0035Version {
|
||||||
|
str := fmt.Sprintf("mempool message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgMemPool.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0035Version {
|
||||||
|
str := fmt.Sprintf("mempool message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgMemPool.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) Command() string {
|
||||||
|
return CmdMemPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgMemPool returns a new bitcoin pong message that conforms to the Message
|
||||||
|
// interface. See MsgPong for details.
|
||||||
|
func NewMsgMemPool() *MsgMemPool {
|
||||||
|
return &MsgMemPool{}
|
||||||
|
}
|
66
wire/msgmempool_test.go
Normal file
66
wire/msgmempool_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemPool(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "mempool"
|
||||||
|
msg := wire.NewMsgMemPool()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgMemPool: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgMemPool failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older protocol versions should fail encode since message didn't
|
||||||
|
// exist yet.
|
||||||
|
oldPver := wire.BIP0035Version - 1
|
||||||
|
err = msg.BtcEncode(&buf, oldPver)
|
||||||
|
if err == nil {
|
||||||
|
s := "encode of MsgMemPool passed for old protocol version %v err <%v>"
|
||||||
|
t.Errorf(s, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := wire.NewMsgMemPool()
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgMemPool failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older protocol versions should fail decode since message didn't
|
||||||
|
// exist yet.
|
||||||
|
err = readmsg.BtcDecode(&buf, oldPver)
|
||||||
|
if err == nil {
|
||||||
|
s := "decode of MsgMemPool passed for old protocol version %v err <%v>"
|
||||||
|
t.Errorf(s, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
163
wire/msgmerkleblock.go
Normal file
163
wire/msgmerkleblock.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxFlagsPerMerkleBlock is the maximum number of flag bytes that could
|
||||||
|
// possibly fit into a merkle block. Since each transaction is represented by
|
||||||
|
// a single bit, this is the max number of transactions per block divided by
|
||||||
|
// 8 bits per byte. Then an extra one to cover partials.
|
||||||
|
const maxFlagsPerMerkleBlock = maxTxPerBlock / 8
|
||||||
|
|
||||||
|
// MsgMerkleBlock implements the Message interface and represents a bitcoin
|
||||||
|
// merkleblock message which is used to reset a Bloom filter.
|
||||||
|
//
|
||||||
|
// This message was not added until protocol version BIP0037Version.
|
||||||
|
type MsgMerkleBlock struct {
|
||||||
|
Header BlockHeader
|
||||||
|
Transactions uint32
|
||||||
|
Hashes []*ShaHash
|
||||||
|
Flags []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxHash adds a new transaction hash to the message.
|
||||||
|
func (msg *MsgMerkleBlock) AddTxHash(hash *ShaHash) error {
|
||||||
|
if len(msg.Hashes)+1 > maxTxPerBlock {
|
||||||
|
str := fmt.Sprintf("too many tx hashes for message [max %v]",
|
||||||
|
maxTxPerBlock)
|
||||||
|
return messageError("MsgMerkleBlock.AddTxHash", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Hashes = append(msg.Hashes, hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("merkleblock message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgMerkleBlock.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := readBlockHeader(r, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.Transactions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num block locator hashes and limit to max.
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > maxTxPerBlock {
|
||||||
|
str := fmt.Sprintf("too many transaction hashes for message "+
|
||||||
|
"[count %v, max %v]", count, maxTxPerBlock)
|
||||||
|
return messageError("MsgMerkleBlock.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Hashes = make([]*ShaHash, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
var sha ShaHash
|
||||||
|
err := readElement(r, &sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddTxHash(&sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Flags, err = readVarBytes(r, pver, maxFlagsPerMerkleBlock,
|
||||||
|
"merkle block flags size")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0037Version {
|
||||||
|
str := fmt.Sprintf("merkleblock message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgMerkleBlock.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num transaction hashes and limit to max.
|
||||||
|
numHashes := len(msg.Hashes)
|
||||||
|
if numHashes > maxTxPerBlock {
|
||||||
|
str := fmt.Sprintf("too many transaction hashes for message "+
|
||||||
|
"[count %v, max %v]", numHashes, maxTxPerBlock)
|
||||||
|
return messageError("MsgMerkleBlock.BtcDecode", str)
|
||||||
|
}
|
||||||
|
numFlagBytes := len(msg.Flags)
|
||||||
|
if numFlagBytes > maxFlagsPerMerkleBlock {
|
||||||
|
str := fmt.Sprintf("too many flag bytes for message [count %v, "+
|
||||||
|
"max %v]", numFlagBytes, maxFlagsPerMerkleBlock)
|
||||||
|
return messageError("MsgMerkleBlock.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeBlockHeader(w, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.Transactions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(numHashes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, hash := range msg.Hashes {
|
||||||
|
err = writeElement(w, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarBytes(w, pver, msg.Flags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgMerkleBlock) Command() string {
|
||||||
|
return CmdMerkleBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return MaxBlockPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to
|
||||||
|
// the Message interface. See MsgMerkleBlock for details.
|
||||||
|
func NewMsgMerkleBlock(bh *BlockHeader) *MsgMerkleBlock {
|
||||||
|
return &MsgMerkleBlock{
|
||||||
|
Header: *bh,
|
||||||
|
Transactions: 0,
|
||||||
|
Hashes: make([]*ShaHash, 0),
|
||||||
|
Flags: make([]byte, 0),
|
||||||
|
}
|
||||||
|
}
|
426
wire/msgmerkleblock_test.go
Normal file
426
wire/msgmerkleblock_test.go
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMerkleBlock tests the MsgMerkleBlock API.
|
||||||
|
func TestMerkleBlock(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 1 header.
|
||||||
|
prevHash := &blockOne.Header.PrevBlock
|
||||||
|
merkleHash := &blockOne.Header.MerkleRoot
|
||||||
|
bits := blockOne.Header.Bits
|
||||||
|
nonce := blockOne.Header.Nonce
|
||||||
|
bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "merkleblock"
|
||||||
|
msg := wire.NewMsgMerkleBlock(bh)
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgBlock: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(1000000)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load maxTxPerBlock hashes
|
||||||
|
data := make([]byte, 32)
|
||||||
|
for i := 0; i < wire.MaxTxPerBlock; i++ {
|
||||||
|
rand.Read(data)
|
||||||
|
hash, err := wire.NewShaHash(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHash failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = msg.AddTxHash(hash); err != nil {
|
||||||
|
t.Errorf("AddTxHash failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one more Tx to test failure.
|
||||||
|
rand.Read(data)
|
||||||
|
hash, err := wire.NewShaHash(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHash failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = msg.AddTxHash(hash); err == nil {
|
||||||
|
t.Errorf("AddTxHash succeeded when it should have failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgMerkleBlock failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := wire.MsgMerkleBlock{}
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgMerkleBlock failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force extra hash to test maxTxPerBlock.
|
||||||
|
msg.Hashes = append(msg.Hashes, hash)
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgMerkleBlock succeeded with too many " +
|
||||||
|
"tx hashes when it should have failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force too many flag bytes to test maxFlagsPerMerkleBlock.
|
||||||
|
// Reset the number of hashes back to a valid value.
|
||||||
|
msg.Hashes = msg.Hashes[len(msg.Hashes)-1:]
|
||||||
|
msg.Flags = make([]byte, wire.MaxFlagsPerMerkleBlock+1)
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgMerkleBlock succeeded with too many " +
|
||||||
|
"flag bytes when it should have failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMerkleBlockCrossProtocol tests the MsgMerkleBlock API when encoding with
|
||||||
|
// the latest protocol version and decoding with BIP0031Version.
|
||||||
|
func TestMerkleBlockCrossProtocol(t *testing.T) {
|
||||||
|
// Block 1 header.
|
||||||
|
prevHash := &blockOne.Header.PrevBlock
|
||||||
|
merkleHash := &blockOne.Header.MerkleRoot
|
||||||
|
bits := blockOne.Header.Bits
|
||||||
|
nonce := blockOne.Header.Nonce
|
||||||
|
bh := wire.NewBlockHeader(prevHash, merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
msg := wire.NewMsgMerkleBlock(bh)
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of NewMsgFilterLoad failed %v err <%v>", msg,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
var readmsg wire.MsgFilterLoad
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMerkleBlockWire tests the MsgMerkleBlock wire encode and decode for
|
||||||
|
// various numbers of transaction hashes and protocol versions.
|
||||||
|
func TestMerkleBlockWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgMerkleBlock // Message to encode
|
||||||
|
out *wire.MsgMerkleBlock // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, &merkleBlockOne, merkleBlockOneBytes,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0037Version.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, &merkleBlockOne, merkleBlockOneBytes,
|
||||||
|
wire.BIP0037Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgMerkleBlock
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMerkleBlockWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgBlock to confirm error paths work correctly.
|
||||||
|
func TestMerkleBlockWireErrors(t *testing.T) {
|
||||||
|
// Use protocol version 70001 specifically here instead of the latest
|
||||||
|
// because the test data is using bytes encoded with that protocol
|
||||||
|
// version.
|
||||||
|
pver := uint32(70001)
|
||||||
|
pverNoMerkleBlock := wire.BIP0037Version - 1
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgMerkleBlock // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in version.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 0,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in prev block hash.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 4,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in merkle root.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 36,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in timestamp.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 68,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in difficulty bits.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 72,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in header nonce.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 76,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in transaction count.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 80,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in num hashes.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 84,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in hashes.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 85,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in num flag bytes.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 117,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error in flag bytes.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pver, 118,
|
||||||
|
io.ErrShortWrite, io.EOF,
|
||||||
|
},
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{
|
||||||
|
&merkleBlockOne, merkleBlockOneBytes, pverNoMerkleBlock,
|
||||||
|
119, wireErr, wireErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgMerkleBlock
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMerkleBlockOverflowErrors performs tests to ensure encoding and decoding
|
||||||
|
// merkle blocks that are intentionally crafted to use large values for the
|
||||||
|
// number of hashes and flags are handled properly. This could otherwise
|
||||||
|
// potentially be used as an attack vector.
|
||||||
|
func TestMerkleBlockOverflowErrors(t *testing.T) {
|
||||||
|
// Use protocol version 70001 specifically here instead of the latest
|
||||||
|
// protocol version because the test data is using bytes encoded with
|
||||||
|
// that version.
|
||||||
|
pver := uint32(70001)
|
||||||
|
|
||||||
|
// Create bytes for a merkle block that claims to have more than the max
|
||||||
|
// allowed tx hashes.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
wire.TstWriteVarInt(&buf, pver, wire.MaxTxPerBlock+1)
|
||||||
|
numHashesOffset := 84
|
||||||
|
exceedMaxHashes := make([]byte, numHashesOffset)
|
||||||
|
copy(exceedMaxHashes, merkleBlockOneBytes[:numHashesOffset])
|
||||||
|
exceedMaxHashes = append(exceedMaxHashes, buf.Bytes()...)
|
||||||
|
|
||||||
|
// Create bytes for a merkle block that claims to have more than the max
|
||||||
|
// allowed flag bytes.
|
||||||
|
buf.Reset()
|
||||||
|
wire.TstWriteVarInt(&buf, pver, wire.MaxFlagsPerMerkleBlock+1)
|
||||||
|
numFlagBytesOffset := 117
|
||||||
|
exceedMaxFlagBytes := make([]byte, numFlagBytesOffset)
|
||||||
|
copy(exceedMaxFlagBytes, merkleBlockOneBytes[:numFlagBytesOffset])
|
||||||
|
exceedMaxFlagBytes = append(exceedMaxFlagBytes, buf.Bytes()...)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
err error // Expected error
|
||||||
|
}{
|
||||||
|
// Block that claims to have more than max allowed hashes.
|
||||||
|
{exceedMaxHashes, pver, &wire.MessageError{}},
|
||||||
|
// Block that claims to have more than max allowed flag bytes.
|
||||||
|
{exceedMaxFlagBytes, pver, &wire.MessageError{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgMerkleBlock
|
||||||
|
r := bytes.NewReader(test.buf)
|
||||||
|
err := msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkleBlockOne is a merkle block created from block one of the block chain
|
||||||
|
// where the first transaction matches.
|
||||||
|
var merkleBlockOne = wire.MsgMerkleBlock{
|
||||||
|
Header: wire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}),
|
||||||
|
MerkleRoot: wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e,
|
||||||
|
}),
|
||||||
|
Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST
|
||||||
|
Bits: 0x1d00ffff, // 486604799
|
||||||
|
Nonce: 0x9962e301, // 2573394689
|
||||||
|
},
|
||||||
|
Transactions: 1,
|
||||||
|
Hashes: []*wire.ShaHash{
|
||||||
|
(*wire.ShaHash)(&[wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Flags: []byte{0x80},
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkleBlockOneBytes is the serialized bytes for a merkle block created from
|
||||||
|
// block one of the block chain where the first transation matches.
|
||||||
|
var merkleBlockOneBytes = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x01, 0x00, 0x00, 0x00, // TxnCount
|
||||||
|
0x01, // Num hashes
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // Hash
|
||||||
|
0x01, // Num flag bytes
|
||||||
|
0x80, // Flags
|
||||||
|
}
|
107
wire/msgnotfound.go
Normal file
107
wire/msgnotfound.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgNotFound defines a bitcoin notfound message which is sent in response to
|
||||||
|
// a getdata message if any of the requested data in not available on the peer.
|
||||||
|
// Each message is limited to a maximum number of inventory vectors, which is
|
||||||
|
// currently 50,000.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending a notfound message to another peer.
|
||||||
|
type MsgNotFound struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgNotFound) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [max %v]",
|
||||||
|
MaxInvPerMsg)
|
||||||
|
return messageError("MsgNotFound.AddInvVect", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgNotFound.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = make([]*InvVect, 0, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := fmt.Sprintf("too many invvect in message [%v]", count)
|
||||||
|
return messageError("MsgNotFound.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) Command() string {
|
||||||
|
return CmdNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Max var int 9 bytes + max InvVects at 36 bytes each.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return MaxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgNotFound returns a new bitcoin notfound message that conforms to the
|
||||||
|
// Message interface. See MsgNotFound for details.
|
||||||
|
func NewMsgNotFound() *MsgNotFound {
|
||||||
|
return &MsgNotFound{
|
||||||
|
InvList: make([]*InvVect, 0, defaultInvListAlloc),
|
||||||
|
}
|
||||||
|
}
|
321
wire/msgnotfound_test.go
Normal file
321
wire/msgnotfound_test.go
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNotFound tests the MsgNotFound API.
|
||||||
|
func TestNotFound(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "notfound"
|
||||||
|
msg := wire.NewMsgNotFound()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgNotFound: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := wire.ShaHash{}
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNotFoundWire tests the MsgNotFound wire encode and decode for various
|
||||||
|
// numbers of inventory vectors and protocol versions.
|
||||||
|
func TestNotFoundWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
iv2 := wire.NewInvVect(wire.InvTypeTx, txHash)
|
||||||
|
|
||||||
|
// Empty notfound message.
|
||||||
|
NoInv := wire.NewMsgNotFound()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFound message with multiple inventory vectors.
|
||||||
|
MultiInv := wire.NewMsgNotFound()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvTypeTx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgNotFound // Message to encode
|
||||||
|
out *wire.MsgNotFound // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgNotFound
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNotFoundWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgNotFound to confirm error paths work correctly.
|
||||||
|
func TestNotFoundWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := wire.NewInvVect(wire.InvTypeBlock, blockHash)
|
||||||
|
|
||||||
|
// Base message used to induce errors.
|
||||||
|
baseNotFound := wire.NewMsgNotFound()
|
||||||
|
baseNotFound.AddInvVect(iv)
|
||||||
|
baseNotFoundEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvTypeBlock
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message that forces an error by having more than the max allowed inv
|
||||||
|
// vectors.
|
||||||
|
maxNotFound := wire.NewMsgNotFound()
|
||||||
|
for i := 0; i < wire.MaxInvPerMsg; i++ {
|
||||||
|
maxNotFound.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
maxNotFound.InvList = append(maxNotFound.InvList, iv)
|
||||||
|
maxNotFoundEncoded := []byte{
|
||||||
|
0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgNotFound // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in inventory vector count
|
||||||
|
{baseNotFound, baseNotFoundEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in inventory list.
|
||||||
|
{baseNotFound, baseNotFoundEncoded, pver, 1, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error with greater than max inventory vectors.
|
||||||
|
{maxNotFound, maxNotFoundEncoded, pver, 3, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgNotFound
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
wire/msgping.go
Normal file
87
wire/msgping.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgPing implements the Message interface and represents a bitcoin ping
|
||||||
|
// message.
|
||||||
|
//
|
||||||
|
// For versions BIP0031Version and earlier, it is used primarily to confirm
|
||||||
|
// that a connection is still valid. A transmission error is typically
|
||||||
|
// interpreted as a closed connection and that the peer should be removed.
|
||||||
|
// For versions AFTER BIP0031Version it contains an identifier which can be
|
||||||
|
// returned in the pong message to determine network timing.
|
||||||
|
//
|
||||||
|
// The payload for this message just consists of a nonce used for identifying
|
||||||
|
// it later.
|
||||||
|
type MsgPing struct {
|
||||||
|
// Unique value associated with message that is used to identify
|
||||||
|
// specific ping message.
|
||||||
|
Nonce uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
err := readElement(r, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
err := writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) Command() string {
|
||||||
|
return CmdPing
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
plen := uint32(0)
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
// Nonce 8 bytes.
|
||||||
|
plen += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgPing returns a new bitcoin ping message that conforms to the Message
|
||||||
|
// interface. See MsgPing for details.
|
||||||
|
func NewMsgPing(nonce uint64) *MsgPing {
|
||||||
|
return &MsgPing{
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
}
|
243
wire/msgping_test.go
Normal file
243
wire/msgping_test.go
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPing tests the MsgPing API against the latest protocol version.
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure we get the same nonce back out.
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "ping"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgPing: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(8)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingBIP0031 tests the MsgPing API against the protocol version
|
||||||
|
// BIP0031Version.
|
||||||
|
func TestPingBIP0031(t *testing.T) {
|
||||||
|
// Use the protocol version just prior to BIP0031Version changes.
|
||||||
|
pver := wire.BIP0031Version
|
||||||
|
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for old protocol version.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with old protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPing failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with old protocol version.
|
||||||
|
readmsg := wire.NewMsgPing(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this protocol version doesn't support the nonce, make sure
|
||||||
|
// it didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Errorf("Should not get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingCrossProtocol tests the MsgPing API when encoding with the latest
|
||||||
|
// protocol version and decoding with BIP0031Version.
|
||||||
|
func TestPingCrossProtocol(t *testing.T) {
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPing failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
readmsg := wire.NewMsgPing(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the nonce, make
|
||||||
|
// sure it didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Error("Should not get same nonce for cross protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingWire tests the MsgPing wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestPingWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.MsgPing // Message to encode
|
||||||
|
out wire.MsgPing // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
wire.MsgPing{Nonce: 123123}, // 0x1e0f3
|
||||||
|
wire.MsgPing{Nonce: 123123}, // 0x1e0f3
|
||||||
|
[]byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version+1
|
||||||
|
{
|
||||||
|
wire.MsgPing{Nonce: 456456}, // 0x6f708
|
||||||
|
wire.MsgPing{Nonce: 456456}, // 0x6f708
|
||||||
|
[]byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
wire.BIP0031Version + 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version
|
||||||
|
{
|
||||||
|
wire.MsgPing{Nonce: 789789}, // 0xc0d1d
|
||||||
|
wire.MsgPing{Nonce: 0}, // No nonce for pver
|
||||||
|
[]byte{}, // No nonce for pver
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgPing
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgPing to confirm error paths work correctly.
|
||||||
|
func TestPingWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgPing // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
{
|
||||||
|
&wire.MsgPing{Nonce: 123123}, // 0x1e0f3
|
||||||
|
[]byte{0xf3, 0xe0, 0x01, 0x00},
|
||||||
|
pver,
|
||||||
|
2,
|
||||||
|
io.ErrShortWrite,
|
||||||
|
io.ErrUnexpectedEOF,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgPing
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
wire/msgpong.go
Normal file
88
wire/msgpong.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgPong implements the Message interface and represents a bitcoin pong
|
||||||
|
// message which is used primarily to confirm that a connection is still valid
|
||||||
|
// in response to a bitcoin ping message (MsgPing).
|
||||||
|
//
|
||||||
|
// This message was not added until protocol versions AFTER BIP0031Version.
|
||||||
|
type MsgPong struct {
|
||||||
|
// Unique value associated with message that is used to identify
|
||||||
|
// specific ping message.
|
||||||
|
Nonce uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
// NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver <= BIP0031Version {
|
||||||
|
str := fmt.Sprintf("pong message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgPong.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := readElement(r, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver <= BIP0031Version {
|
||||||
|
str := fmt.Sprintf("pong message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgPong.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) Command() string {
|
||||||
|
return CmdPong
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
plen := uint32(0)
|
||||||
|
// The pong message did not exist for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
// Nonce 8 bytes.
|
||||||
|
plen += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgPong returns a new bitcoin pong message that conforms to the Message
|
||||||
|
// interface. See MsgPong for details.
|
||||||
|
func NewMsgPong(nonce uint64) *MsgPong {
|
||||||
|
return &MsgPong{
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
}
|
276
wire/msgpong_test.go
Normal file
276
wire/msgpong_test.go
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPongLatest tests the MsgPong API against the latest protocol version.
|
||||||
|
func TestPongLatest(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPong: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "pong"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgPong: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(8)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPong failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := wire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPong failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure nonce is the same.
|
||||||
|
if msg.Nonce != readmsg.Nonce {
|
||||||
|
t.Errorf("Should get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongBIP0031 tests the MsgPong API against the protocol version
|
||||||
|
// BIP0031Version.
|
||||||
|
func TestPongBIP0031(t *testing.T) {
|
||||||
|
// Use the protocol version just prior to BIP0031Version changes.
|
||||||
|
pver := wire.BIP0031Version
|
||||||
|
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("Should get same nonce back out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for old protocol version.
|
||||||
|
size := msg.MaxPayloadLength(pver)
|
||||||
|
if size != 0 {
|
||||||
|
t.Errorf("Max length should be 0 for pong protocol version %d.",
|
||||||
|
pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with old protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgPong succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with old protocol version.
|
||||||
|
readmsg := wire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgPong succeeded when it shouldn't have %v",
|
||||||
|
spew.Sdump(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this protocol version doesn't support pong, make sure the
|
||||||
|
// nonce didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Errorf("Should not get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongCrossProtocol tests the MsgPong API when encoding with the latest
|
||||||
|
// protocol version and decoding with BIP0031Version.
|
||||||
|
func TestPongCrossProtocol(t *testing.T) {
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := wire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("Should get same nonce back out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPong failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
readmsg := wire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, wire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgPong succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the pong message,
|
||||||
|
// make sure the nonce didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Error("Should not get same nonce for cross protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongWire tests the MsgPong wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestPongWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.MsgPong // Message to encode
|
||||||
|
out wire.MsgPong // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
wire.MsgPong{Nonce: 123123}, // 0x1e0f3
|
||||||
|
wire.MsgPong{Nonce: 123123}, // 0x1e0f3
|
||||||
|
[]byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version+1
|
||||||
|
{
|
||||||
|
wire.MsgPong{Nonce: 456456}, // 0x6f708
|
||||||
|
wire.MsgPong{Nonce: 456456}, // 0x6f708
|
||||||
|
[]byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
wire.BIP0031Version + 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgPong
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgPong to confirm error paths work correctly.
|
||||||
|
func TestPongWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverNoPong := wire.BIP0031Version
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
basePong := wire.NewMsgPong(123123) // 0x1e0f3
|
||||||
|
basePongEncoded := []byte{
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgPong // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in nonce.
|
||||||
|
{basePong, basePongEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{basePong, basePongEncoded, pverNoPong, 4, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgPong
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
184
wire/msgreject.go
Normal file
184
wire/msgreject.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RejectCode represents a numeric value by which a remote peer indicates
|
||||||
|
// why a message was rejected.
|
||||||
|
type RejectCode uint8
|
||||||
|
|
||||||
|
// These constants define the various supported reject codes.
|
||||||
|
const (
|
||||||
|
RejectMalformed RejectCode = 0x01
|
||||||
|
RejectInvalid RejectCode = 0x10
|
||||||
|
RejectObsolete RejectCode = 0x11
|
||||||
|
RejectDuplicate RejectCode = 0x12
|
||||||
|
RejectNonstandard RejectCode = 0x40
|
||||||
|
RejectDust RejectCode = 0x41
|
||||||
|
RejectInsufficientFee RejectCode = 0x42
|
||||||
|
RejectCheckpoint RejectCode = 0x43
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of reject codes back strings for pretty printing.
|
||||||
|
var rejectCodeStrings = map[RejectCode]string{
|
||||||
|
RejectMalformed: "REJECT_MALFORMED",
|
||||||
|
RejectInvalid: "REJECT_INVALID",
|
||||||
|
RejectObsolete: "REJECT_OBSOLETE",
|
||||||
|
RejectDuplicate: "REJECT_DUPLICATE",
|
||||||
|
RejectNonstandard: "REJECT_NONSTANDARD",
|
||||||
|
RejectDust: "REJECT_DUST",
|
||||||
|
RejectInsufficientFee: "REJECT_INSUFFICIENTFEE",
|
||||||
|
RejectCheckpoint: "REJECT_CHECKPOINT",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the RejectCode in human-readable form.
|
||||||
|
func (code RejectCode) String() string {
|
||||||
|
if s, ok := rejectCodeStrings[code]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("Unknown RejectCode (%d)", uint8(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgReject implements the Message interface and represents a bitcoin reject
|
||||||
|
// message.
|
||||||
|
//
|
||||||
|
// This message was not added until protocol version RejectVersion.
|
||||||
|
type MsgReject struct {
|
||||||
|
// Cmd is the command for the message which was rejected such as
|
||||||
|
// as CmdBlock or CmdTx. This can be obtained from the Command function
|
||||||
|
// of a Message.
|
||||||
|
Cmd string
|
||||||
|
|
||||||
|
// RejectCode is a code indicating why the command was rejected. It
|
||||||
|
// is encoded as a uint8 on the wire.
|
||||||
|
Code RejectCode
|
||||||
|
|
||||||
|
// Reason is a human-readable string with specific details (over and
|
||||||
|
// above the reject code) about why the command was rejected.
|
||||||
|
Reason string
|
||||||
|
|
||||||
|
// Hash identifies a specific block or transaction that was rejected
|
||||||
|
// and therefore only applies the MsgBlock and MsgTx messages.
|
||||||
|
Hash ShaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgReject) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < RejectVersion {
|
||||||
|
str := fmt.Sprintf("reject message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgReject.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command that was rejected.
|
||||||
|
cmd, err := readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Cmd = cmd
|
||||||
|
|
||||||
|
// Code indicating why the command was rejected.
|
||||||
|
err = readElement(r, &msg.Code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human readable string with specific details (over and above the
|
||||||
|
// reject code above) about why the command was rejected.
|
||||||
|
reason, err := readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Reason = reason
|
||||||
|
|
||||||
|
// CmdBlock and CmdTx messages have an additional hash field that
|
||||||
|
// identifies the specific block or transaction.
|
||||||
|
if msg.Cmd == CmdBlock || msg.Cmd == CmdTx {
|
||||||
|
err := readElement(r, &msg.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgReject) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < RejectVersion {
|
||||||
|
str := fmt.Sprintf("reject message invalid for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
return messageError("MsgReject.BtcEncode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command that was rejected.
|
||||||
|
err := writeVarString(w, pver, msg.Cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code indicating why the command was rejected.
|
||||||
|
err = writeElement(w, msg.Code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human readable string with specific details (over and above the
|
||||||
|
// reject code above) about why the command was rejected.
|
||||||
|
err = writeVarString(w, pver, msg.Reason)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdBlock and CmdTx messages have an additional hash field that
|
||||||
|
// identifies the specific block or transaction.
|
||||||
|
if msg.Cmd == CmdBlock || msg.Cmd == CmdTx {
|
||||||
|
err := writeElement(w, &msg.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgReject) Command() string {
|
||||||
|
return CmdReject
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgReject) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
plen := uint32(0)
|
||||||
|
// The reject message did not exist before protocol version
|
||||||
|
// RejectVersion.
|
||||||
|
if pver >= RejectVersion {
|
||||||
|
// Unfortunately the bitcoin protocol does not enforce a sane
|
||||||
|
// limit on the length of the reason, so the max payload is the
|
||||||
|
// overall maximum message payload.
|
||||||
|
plen = MaxMessagePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgReject returns a new bitcoin reject message that conforms to the
|
||||||
|
// Message interface. See MsgReject for details.
|
||||||
|
func NewMsgReject(command string, code RejectCode, reason string) *MsgReject {
|
||||||
|
return &MsgReject{
|
||||||
|
Cmd: command,
|
||||||
|
Code: code,
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
}
|
385
wire/msgreject_test.go
Normal file
385
wire/msgreject_test.go
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
// Copyright (c) 2014-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRejectCodeStringer tests the stringized output for the reject code type.
|
||||||
|
func TestRejectCodeStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.RejectCode
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{wire.RejectMalformed, "REJECT_MALFORMED"},
|
||||||
|
{wire.RejectInvalid, "REJECT_INVALID"},
|
||||||
|
{wire.RejectObsolete, "REJECT_OBSOLETE"},
|
||||||
|
{wire.RejectDuplicate, "REJECT_DUPLICATE"},
|
||||||
|
{wire.RejectNonstandard, "REJECT_NONSTANDARD"},
|
||||||
|
{wire.RejectDust, "REJECT_DUST"},
|
||||||
|
{wire.RejectInsufficientFee, "REJECT_INSUFFICIENTFEE"},
|
||||||
|
{wire.RejectCheckpoint, "REJECT_CHECKPOINT"},
|
||||||
|
{0xff, "Unknown RejectCode (255)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRejectLatest tests the MsgPong API against the latest protocol version.
|
||||||
|
func TestRejectLatest(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Create reject message data.
|
||||||
|
rejCommand := (&wire.MsgBlock{}).Command()
|
||||||
|
rejCode := wire.RejectDuplicate
|
||||||
|
rejReason := "duplicate block"
|
||||||
|
rejHash := mainNetGenesisHash
|
||||||
|
|
||||||
|
// Ensure we get the correct data back out.
|
||||||
|
msg := wire.NewMsgReject(rejCommand, rejCode, rejReason)
|
||||||
|
msg.Hash = rejHash
|
||||||
|
if msg.Cmd != rejCommand {
|
||||||
|
t.Errorf("NewMsgReject: wrong rejected command - got %v, "+
|
||||||
|
"want %v", msg.Cmd, rejCommand)
|
||||||
|
}
|
||||||
|
if msg.Code != rejCode {
|
||||||
|
t.Errorf("NewMsgReject: wrong rejected code - got %v, "+
|
||||||
|
"want %v", msg.Code, rejCode)
|
||||||
|
}
|
||||||
|
if msg.Reason != rejReason {
|
||||||
|
t.Errorf("NewMsgReject: wrong rejected reason - got %v, "+
|
||||||
|
"want %v", msg.Reason, rejReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "reject"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgReject: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(wire.MaxMessagePayload)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgReject failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readMsg := wire.MsgReject{}
|
||||||
|
err = readMsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgReject failed %v err <%v>", buf.Bytes(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure decoded data is the same.
|
||||||
|
if msg.Cmd != readMsg.Cmd {
|
||||||
|
t.Errorf("Should get same reject command - got %v, want %v",
|
||||||
|
readMsg.Cmd, msg.Cmd)
|
||||||
|
}
|
||||||
|
if msg.Code != readMsg.Code {
|
||||||
|
t.Errorf("Should get same reject code - got %v, want %v",
|
||||||
|
readMsg.Code, msg.Code)
|
||||||
|
}
|
||||||
|
if msg.Reason != readMsg.Reason {
|
||||||
|
t.Errorf("Should get same reject reason - got %v, want %v",
|
||||||
|
readMsg.Reason, msg.Reason)
|
||||||
|
}
|
||||||
|
if msg.Hash != readMsg.Hash {
|
||||||
|
t.Errorf("Should get same reject hash - got %v, want %v",
|
||||||
|
readMsg.Hash, msg.Hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRejectBeforeAdded tests the MsgReject API against a protocol version
|
||||||
|
// before the version which introduced it (RejectVersion).
|
||||||
|
func TestRejectBeforeAdded(t *testing.T) {
|
||||||
|
// Use the protocol version just prior to RejectVersion.
|
||||||
|
pver := wire.RejectVersion - 1
|
||||||
|
|
||||||
|
// Create reject message data.
|
||||||
|
rejCommand := (&wire.MsgBlock{}).Command()
|
||||||
|
rejCode := wire.RejectDuplicate
|
||||||
|
rejReason := "duplicate block"
|
||||||
|
rejHash := mainNetGenesisHash
|
||||||
|
|
||||||
|
msg := wire.NewMsgReject(rejCommand, rejCode, rejReason)
|
||||||
|
msg.Hash = rejHash
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for old protocol version.
|
||||||
|
size := msg.MaxPayloadLength(pver)
|
||||||
|
if size != 0 {
|
||||||
|
t.Errorf("Max length should be 0 for reject protocol version %d.",
|
||||||
|
pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with old protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgReject succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Test decode with old protocol version.
|
||||||
|
readMsg := wire.MsgReject{}
|
||||||
|
err = readMsg.BtcDecode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgReject succeeded when it shouldn't "+
|
||||||
|
"have %v", spew.Sdump(buf.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this protocol version doesn't support reject, make sure various
|
||||||
|
// fields didn't get encoded and decoded back out.
|
||||||
|
if msg.Cmd == readMsg.Cmd {
|
||||||
|
t.Errorf("Should not get same reject command for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
}
|
||||||
|
if msg.Code == readMsg.Code {
|
||||||
|
t.Errorf("Should not get same reject code for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
}
|
||||||
|
if msg.Reason == readMsg.Reason {
|
||||||
|
t.Errorf("Should not get same reject reason for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
}
|
||||||
|
if msg.Hash == readMsg.Hash {
|
||||||
|
t.Errorf("Should not get same reject hash for protocol "+
|
||||||
|
"version %d", pver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRejectCrossProtocol tests the MsgReject API when encoding with the latest
|
||||||
|
// protocol version and decoded with a version before the version which
|
||||||
|
// introduced it (RejectVersion).
|
||||||
|
func TestRejectCrossProtocol(t *testing.T) {
|
||||||
|
// Create reject message data.
|
||||||
|
rejCommand := (&wire.MsgBlock{}).Command()
|
||||||
|
rejCode := wire.RejectDuplicate
|
||||||
|
rejReason := "duplicate block"
|
||||||
|
rejHash := mainNetGenesisHash
|
||||||
|
|
||||||
|
msg := wire.NewMsgReject(rejCommand, rejCode, rejReason)
|
||||||
|
msg.Hash = rejHash
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, wire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgReject failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
readMsg := wire.MsgReject{}
|
||||||
|
err = readMsg.BtcDecode(&buf, wire.RejectVersion-1)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgReject succeeded when it shouldn't "+
|
||||||
|
"have %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the reject
|
||||||
|
// message, make sure the various fields didn't get encoded and decoded
|
||||||
|
// back out.
|
||||||
|
if msg.Cmd == readMsg.Cmd {
|
||||||
|
t.Errorf("Should not get same reject command for cross protocol")
|
||||||
|
}
|
||||||
|
if msg.Code == readMsg.Code {
|
||||||
|
t.Errorf("Should not get same reject code for cross protocol")
|
||||||
|
}
|
||||||
|
if msg.Reason == readMsg.Reason {
|
||||||
|
t.Errorf("Should not get same reject reason for cross protocol")
|
||||||
|
}
|
||||||
|
if msg.Hash == readMsg.Hash {
|
||||||
|
t.Errorf("Should not get same reject hash for cross protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRejectWire tests the MsgReject wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestRejectWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
msg wire.MsgReject // Message to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version rejected command version (no hash).
|
||||||
|
{
|
||||||
|
wire.MsgReject{
|
||||||
|
Cmd: "version",
|
||||||
|
Code: wire.RejectDuplicate,
|
||||||
|
Reason: "duplicate version",
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version"
|
||||||
|
0x12, // wire.RejectDuplicate
|
||||||
|
0x11, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61,
|
||||||
|
0x74, 0x65, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||||
|
0x6f, 0x6e, // "duplicate version"
|
||||||
|
},
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
// Latest protocol version rejected command block (has hash).
|
||||||
|
{
|
||||||
|
wire.MsgReject{
|
||||||
|
Cmd: "block",
|
||||||
|
Code: wire.RejectDuplicate,
|
||||||
|
Reason: "duplicate block",
|
||||||
|
Hash: mainNetGenesisHash,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "block"
|
||||||
|
0x12, // wire.RejectDuplicate
|
||||||
|
0x0f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61,
|
||||||
|
0x74, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "duplicate block"
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // mainNetGenesisHash
|
||||||
|
},
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.msg.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgReject
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.msg) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.msg))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRejectWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgReject to confirm error paths work correctly.
|
||||||
|
func TestRejectWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverNoReject := wire.RejectVersion - 1
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
baseReject := wire.NewMsgReject("block", wire.RejectDuplicate,
|
||||||
|
"duplicate block")
|
||||||
|
baseReject.Hash = mainNetGenesisHash
|
||||||
|
baseRejectEncoded := []byte{
|
||||||
|
0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "block"
|
||||||
|
0x12, // wire.RejectDuplicate
|
||||||
|
0x0f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61,
|
||||||
|
0x74, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, // "duplicate block"
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // mainNetGenesisHash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgReject // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force error in reject command.
|
||||||
|
{baseReject, baseRejectEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in reject code.
|
||||||
|
{baseReject, baseRejectEncoded, pver, 6, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in reject reason.
|
||||||
|
{baseReject, baseRejectEncoded, pver, 7, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in reject hash.
|
||||||
|
{baseReject, baseRejectEncoded, pver, 23, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error due to unsupported protocol version.
|
||||||
|
{baseReject, baseRejectEncoded, pverNoReject, 6, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgReject
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
562
wire/msgtx.go
Normal file
562
wire/msgtx.go
Normal file
|
@ -0,0 +1,562 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TxVersion is the current latest supported transaction version.
|
||||||
|
TxVersion = 1
|
||||||
|
|
||||||
|
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||||
|
// of a transaction input can be.
|
||||||
|
MaxTxInSequenceNum uint32 = 0xffffffff
|
||||||
|
|
||||||
|
// MaxPrevOutIndex is the maximum index the index field of a previous
|
||||||
|
// outpoint can be.
|
||||||
|
MaxPrevOutIndex uint32 = 0xffffffff
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultTxInOutAlloc is the default size used for the backing array for
|
||||||
|
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||||
|
// but this figure is intended to provide enough space for the number of
|
||||||
|
// inputs and outputs in a typical transaction without needing to grow the
|
||||||
|
// backing array multiple times.
|
||||||
|
const defaultTxInOutAlloc = 15
|
||||||
|
|
||||||
|
const (
|
||||||
|
// minTxInPayload is the minimum payload size for a transaction input.
|
||||||
|
// PreviousOutPoint.Hash + PreviousOutPoint.Index 4 bytes + Varint for
|
||||||
|
// SignatureScript length 1 byte + Sequence 4 bytes.
|
||||||
|
minTxInPayload = 9 + HashSize
|
||||||
|
|
||||||
|
// maxTxInPerMessage is the maximum number of transactions inputs that
|
||||||
|
// a transaction which fits into a message could possibly have.
|
||||||
|
maxTxInPerMessage = (MaxMessagePayload / minTxInPayload) + 1
|
||||||
|
|
||||||
|
// minTxOutPayload is the minimum payload size for a transaction output.
|
||||||
|
// Value 8 bytes + Varint for PkScript length 1 byte.
|
||||||
|
minTxOutPayload = 9
|
||||||
|
|
||||||
|
// maxTxOutPerMessage is the maximum number of transactions outputs that
|
||||||
|
// a transaction which fits into a message could possibly have.
|
||||||
|
maxTxOutPerMessage = (MaxMessagePayload / minTxOutPayload) + 1
|
||||||
|
|
||||||
|
// minTxPayload is the minimum payload size for a transaction. Note
|
||||||
|
// that any realistically usable transaction must have at least one
|
||||||
|
// input or output, but that is a rule enforced at a higher layer, so
|
||||||
|
// it is intentionally not included here.
|
||||||
|
// Version 4 bytes + Varint number of transaction inputs 1 byte + Varint
|
||||||
|
// number of transaction outputs 1 byte + LockTime 4 bytes + min input
|
||||||
|
// payload + min output payload.
|
||||||
|
minTxPayload = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutPoint defines a bitcoin data type that is used to track previous
|
||||||
|
// transaction outputs.
|
||||||
|
type OutPoint struct {
|
||||||
|
Hash ShaHash
|
||||||
|
Index uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutPoint returns a new bitcoin transaction outpoint point with the
|
||||||
|
// provided hash and index.
|
||||||
|
func NewOutPoint(hash *ShaHash, index uint32) *OutPoint {
|
||||||
|
return &OutPoint{
|
||||||
|
Hash: *hash,
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the OutPoint in the human-readable form "hash:index".
|
||||||
|
func (o OutPoint) String() string {
|
||||||
|
// Allocate enough for hash string, colon, and 10 digits. Although
|
||||||
|
// at the time of writing, the number of digits can be no greater than
|
||||||
|
// the length of the decimal representation of maxTxOutPerMessage, the
|
||||||
|
// maximum message payload may increase in the future and this
|
||||||
|
// optimization may go unnoticed, so allocate space for 10 decimal
|
||||||
|
// digits, which will fit any uint32.
|
||||||
|
buf := make([]byte, 2*HashSize+1, 2*HashSize+1+10)
|
||||||
|
copy(buf, o.Hash.String())
|
||||||
|
buf[2*HashSize] = ':'
|
||||||
|
buf = strconv.AppendUint(buf, uint64(o.Index), 10)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxIn defines a bitcoin transaction input.
|
||||||
|
type TxIn struct {
|
||||||
|
PreviousOutPoint OutPoint
|
||||||
|
SignatureScript []byte
|
||||||
|
Sequence uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSize returns the number of bytes it would take to serialize the
|
||||||
|
// the transaction input.
|
||||||
|
func (t *TxIn) SerializeSize() int {
|
||||||
|
// Outpoint Hash 32 bytes + Outpoint Index 4 bytes + Sequence 4 bytes +
|
||||||
|
// serialized varint size for the length of SignatureScript +
|
||||||
|
// SignatureScript bytes.
|
||||||
|
return 40 + VarIntSerializeSize(uint64(len(t.SignatureScript))) +
|
||||||
|
len(t.SignatureScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxIn returns a new bitcoin transaction input with the provided
|
||||||
|
// previous outpoint point and signature script with a default sequence of
|
||||||
|
// MaxTxInSequenceNum.
|
||||||
|
func NewTxIn(prevOut *OutPoint, signatureScript []byte) *TxIn {
|
||||||
|
return &TxIn{
|
||||||
|
PreviousOutPoint: *prevOut,
|
||||||
|
SignatureScript: signatureScript,
|
||||||
|
Sequence: MaxTxInSequenceNum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxOut defines a bitcoin transaction output.
|
||||||
|
type TxOut struct {
|
||||||
|
Value int64
|
||||||
|
PkScript []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSize returns the number of bytes it would take to serialize the
|
||||||
|
// the transaction output.
|
||||||
|
func (t *TxOut) SerializeSize() int {
|
||||||
|
// Value 8 bytes + serialized varint size for the length of PkScript +
|
||||||
|
// PkScript bytes.
|
||||||
|
return 8 + VarIntSerializeSize(uint64(len(t.PkScript))) + len(t.PkScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxOut returns a new bitcoin transaction output with the provided
|
||||||
|
// transaction value and public key script.
|
||||||
|
func NewTxOut(value int64, pkScript []byte) *TxOut {
|
||||||
|
return &TxOut{
|
||||||
|
Value: value,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgTx implements the Message interface and represents a bitcoin tx message.
|
||||||
|
// It is used to deliver transaction information in response to a getdata
|
||||||
|
// message (MsgGetData) for a given transaction.
|
||||||
|
//
|
||||||
|
// Use the AddTxIn and AddTxOut functions to build up the list of transaction
|
||||||
|
// inputs and outputs.
|
||||||
|
type MsgTx struct {
|
||||||
|
Version int32
|
||||||
|
TxIn []*TxIn
|
||||||
|
TxOut []*TxOut
|
||||||
|
LockTime uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxIn adds a transaction input to the message.
|
||||||
|
func (msg *MsgTx) AddTxIn(ti *TxIn) {
|
||||||
|
msg.TxIn = append(msg.TxIn, ti)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxOut adds a transaction output to the message.
|
||||||
|
func (msg *MsgTx) AddTxOut(to *TxOut) {
|
||||||
|
msg.TxOut = append(msg.TxOut, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxSha generates the ShaHash name for the transaction.
|
||||||
|
func (msg *MsgTx) TxSha() (ShaHash, error) {
|
||||||
|
// Encode the transaction and calculate double sha256 on the result.
|
||||||
|
// Ignore the error returns since the only way the encode could fail
|
||||||
|
// is being out of memory or due to nil pointers, both of which would
|
||||||
|
// cause a run-time panic. Also, SetBytes can't fail here due to the
|
||||||
|
// fact DoubleSha256 always returns a []byte of the right size
|
||||||
|
// regardless of input.
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, msg.SerializeSize()))
|
||||||
|
_ = msg.Serialize(buf)
|
||||||
|
var sha ShaHash
|
||||||
|
_ = sha.SetBytes(DoubleSha256(buf.Bytes()))
|
||||||
|
|
||||||
|
// Even though this function can't currently fail, it still returns
|
||||||
|
// a potential error to help future proof the API should a failure
|
||||||
|
// become possible.
|
||||||
|
return sha, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of a transaction so that the original does not get
|
||||||
|
// modified when the copy is manipulated.
|
||||||
|
func (msg *MsgTx) Copy() *MsgTx {
|
||||||
|
// Create new tx and start by copying primitive values and making space
|
||||||
|
// for the transaction inputs and outputs.
|
||||||
|
newTx := MsgTx{
|
||||||
|
Version: msg.Version,
|
||||||
|
TxIn: make([]*TxIn, 0, len(msg.TxIn)),
|
||||||
|
TxOut: make([]*TxOut, 0, len(msg.TxOut)),
|
||||||
|
LockTime: msg.LockTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy the old TxIn data.
|
||||||
|
for _, oldTxIn := range msg.TxIn {
|
||||||
|
// Deep copy the old previous outpoint.
|
||||||
|
oldOutPoint := oldTxIn.PreviousOutPoint
|
||||||
|
newOutPoint := OutPoint{}
|
||||||
|
newOutPoint.Hash.SetBytes(oldOutPoint.Hash[:])
|
||||||
|
newOutPoint.Index = oldOutPoint.Index
|
||||||
|
|
||||||
|
// Deep copy the old signature script.
|
||||||
|
var newScript []byte
|
||||||
|
oldScript := oldTxIn.SignatureScript
|
||||||
|
oldScriptLen := len(oldScript)
|
||||||
|
if oldScriptLen > 0 {
|
||||||
|
newScript = make([]byte, oldScriptLen, oldScriptLen)
|
||||||
|
copy(newScript, oldScript[:oldScriptLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new txIn with the deep copied data and append it to
|
||||||
|
// new Tx.
|
||||||
|
newTxIn := TxIn{
|
||||||
|
PreviousOutPoint: newOutPoint,
|
||||||
|
SignatureScript: newScript,
|
||||||
|
Sequence: oldTxIn.Sequence,
|
||||||
|
}
|
||||||
|
newTx.TxIn = append(newTx.TxIn, &newTxIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy the old TxOut data.
|
||||||
|
for _, oldTxOut := range msg.TxOut {
|
||||||
|
// Deep copy the old PkScript
|
||||||
|
var newScript []byte
|
||||||
|
oldScript := oldTxOut.PkScript
|
||||||
|
oldScriptLen := len(oldScript)
|
||||||
|
if oldScriptLen > 0 {
|
||||||
|
newScript = make([]byte, oldScriptLen, oldScriptLen)
|
||||||
|
copy(newScript, oldScript[:oldScriptLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new txOut with the deep copied data and append it to
|
||||||
|
// new Tx.
|
||||||
|
newTxOut := TxOut{
|
||||||
|
Value: oldTxOut.Value,
|
||||||
|
PkScript: newScript,
|
||||||
|
}
|
||||||
|
newTx.TxOut = append(newTx.TxOut, &newTxOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
// See Deserialize for decoding transactions stored to disk, such as in a
|
||||||
|
// database, as opposed to decoding transactions from the wire.
|
||||||
|
func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
var buf [4]byte
|
||||||
|
_, err := io.ReadFull(r, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Version = int32(binary.LittleEndian.Uint32(buf[:]))
|
||||||
|
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent more input transactions than could possibly fit into a
|
||||||
|
// message. It would be possible to cause memory exhaustion and panics
|
||||||
|
// without a sane upper bound on this count.
|
||||||
|
if count > uint64(maxTxInPerMessage) {
|
||||||
|
str := fmt.Sprintf("too many input transactions to fit into "+
|
||||||
|
"max message size [count %d, max %d]", count,
|
||||||
|
maxTxInPerMessage)
|
||||||
|
return messageError("MsgTx.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.TxIn = make([]*TxIn, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
ti := TxIn{}
|
||||||
|
err = readTxIn(r, pver, msg.Version, &ti)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.TxIn[i] = &ti
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent more output transactions than could possibly fit into a
|
||||||
|
// message. It would be possible to cause memory exhaustion and panics
|
||||||
|
// without a sane upper bound on this count.
|
||||||
|
if count > uint64(maxTxOutPerMessage) {
|
||||||
|
str := fmt.Sprintf("too many output transactions to fit into "+
|
||||||
|
"max message size [count %d, max %d]", count,
|
||||||
|
maxTxOutPerMessage)
|
||||||
|
return messageError("MsgTx.BtcDecode", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.TxOut = make([]*TxOut, count)
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
to := TxOut{}
|
||||||
|
err = readTxOut(r, pver, msg.Version, &to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.TxOut[i] = &to
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.ReadFull(r, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.LockTime = binary.LittleEndian.Uint32(buf[:])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes a transaction from r into the receiver using a format
|
||||||
|
// that is suitable for long-term storage such as a database while respecting
|
||||||
|
// the Version field in the transaction. This function differs from BtcDecode
|
||||||
|
// in that BtcDecode decodes from the bitcoin wire protocol as it was sent
|
||||||
|
// across the network. The wire encoding can technically differ depending on
|
||||||
|
// the protocol version and doesn't even really need to match the format of a
|
||||||
|
// stored transaction at all. As of the time this comment was written, the
|
||||||
|
// encoded transaction is the same in both instances, but there is a distinct
|
||||||
|
// difference and separating the two allows the API to be flexible enough to
|
||||||
|
// deal with changes.
|
||||||
|
func (msg *MsgTx) Deserialize(r io.Reader) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of BtcDecode.
|
||||||
|
return msg.BtcDecode(r, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
// See Serialize for encoding transactions to be stored to disk, such as in a
|
||||||
|
// database, as opposed to encoding transactions for the wire.
|
||||||
|
func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
var buf [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], uint32(msg.Version))
|
||||||
|
_, err := w.Write(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := uint64(len(msg.TxIn))
|
||||||
|
err = writeVarInt(w, pver, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ti := range msg.TxIn {
|
||||||
|
err = writeTxIn(w, pver, msg.Version, ti)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count = uint64(len(msg.TxOut))
|
||||||
|
err = writeVarInt(w, pver, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, to := range msg.TxOut {
|
||||||
|
err = writeTxOut(w, pver, msg.Version, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], msg.LockTime)
|
||||||
|
_, err = w.Write(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize encodes the transaction to w using a format that suitable for
|
||||||
|
// long-term storage such as a database while respecting the Version field in
|
||||||
|
// the transaction. This function differs from BtcEncode in that BtcEncode
|
||||||
|
// encodes the transaction to the bitcoin wire protocol in order to be sent
|
||||||
|
// across the network. The wire encoding can technically differ depending on
|
||||||
|
// the protocol version and doesn't even really need to match the format of a
|
||||||
|
// stored transaction at all. As of the time this comment was written, the
|
||||||
|
// encoded transaction is the same in both instances, but there is a distinct
|
||||||
|
// difference and separating the two allows the API to be flexible enough to
|
||||||
|
// deal with changes.
|
||||||
|
func (msg *MsgTx) Serialize(w io.Writer) error {
|
||||||
|
// At the current time, there is no difference between the wire encoding
|
||||||
|
// at protocol version 0 and the stable long-term storage format. As
|
||||||
|
// a result, make use of BtcEncode.
|
||||||
|
return msg.BtcEncode(w, 0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSize returns the number of bytes it would take to serialize the
|
||||||
|
// the transaction.
|
||||||
|
func (msg *MsgTx) SerializeSize() int {
|
||||||
|
// Version 4 bytes + LockTime 4 bytes + Serialized varint size for the
|
||||||
|
// number of transaction inputs and outputs.
|
||||||
|
n := 8 + VarIntSerializeSize(uint64(len(msg.TxIn))) +
|
||||||
|
VarIntSerializeSize(uint64(len(msg.TxOut)))
|
||||||
|
|
||||||
|
for _, txIn := range msg.TxIn {
|
||||||
|
n += txIn.SerializeSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, txOut := range msg.TxOut {
|
||||||
|
n += txOut.SerializeSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) Command() string {
|
||||||
|
return CmdTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return MaxBlockPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgTx returns a new bitcoin tx message that conforms to the Message
|
||||||
|
// interface. The return instance has a default version of TxVersion and there
|
||||||
|
// are no transaction inputs or outputs. Also, the lock time is set to zero
|
||||||
|
// to indicate the transaction is valid immediately as opposed to some time in
|
||||||
|
// future.
|
||||||
|
func NewMsgTx() *MsgTx {
|
||||||
|
return &MsgTx{
|
||||||
|
Version: TxVersion,
|
||||||
|
TxIn: make([]*TxIn, 0, defaultTxInOutAlloc),
|
||||||
|
TxOut: make([]*TxOut, 0, defaultTxInOutAlloc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readOutPoint reads the next sequence of bytes from r as an OutPoint.
|
||||||
|
func readOutPoint(r io.Reader, pver uint32, version int32, op *OutPoint) error {
|
||||||
|
_, err := io.ReadFull(r, op.Hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
_, err = io.ReadFull(r, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.Index = binary.LittleEndian.Uint32(buf[:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOutPoint encodes op to the bitcoin protocol encoding for an OutPoint
|
||||||
|
// to w.
|
||||||
|
func writeOutPoint(w io.Writer, pver uint32, version int32, op *OutPoint) error {
|
||||||
|
_, err := w.Write(op.Hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], op.Index)
|
||||||
|
_, err = w.Write(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTxIn reads the next sequence of bytes from r as a transaction input
|
||||||
|
// (TxIn).
|
||||||
|
func readTxIn(r io.Reader, pver uint32, version int32, ti *TxIn) error {
|
||||||
|
var op OutPoint
|
||||||
|
err := readOutPoint(r, pver, version, &op)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ti.PreviousOutPoint = op
|
||||||
|
|
||||||
|
ti.SignatureScript, err = readVarBytes(r, pver, MaxMessagePayload,
|
||||||
|
"transaction input signature script")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
_, err = io.ReadFull(r, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ti.Sequence = binary.LittleEndian.Uint32(buf[:])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTxIn encodes ti to the bitcoin protocol encoding for a transaction
|
||||||
|
// input (TxIn) to w.
|
||||||
|
func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn) error {
|
||||||
|
err := writeOutPoint(w, pver, version, &ti.PreviousOutPoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarBytes(w, pver, ti.SignatureScript)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(buf[:], ti.Sequence)
|
||||||
|
_, err = w.Write(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTxOut reads the next sequence of bytes from r as a transaction output
|
||||||
|
// (TxOut).
|
||||||
|
func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error {
|
||||||
|
var buf [8]byte
|
||||||
|
_, err := io.ReadFull(r, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to.Value = int64(binary.LittleEndian.Uint64(buf[:]))
|
||||||
|
|
||||||
|
to.PkScript, err = readVarBytes(r, pver, MaxMessagePayload,
|
||||||
|
"transaction output public key script")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTxOut encodes to into the bitcoin protocol encoding for a transaction
|
||||||
|
// output (TxOut) to w.
|
||||||
|
func writeTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error {
|
||||||
|
var buf [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(buf[:], uint64(to.Value))
|
||||||
|
_, err := w.Write(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarBytes(w, pver, to.PkScript)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
692
wire/msgtx_test.go
Normal file
692
wire/msgtx_test.go
Normal file
|
@ -0,0 +1,692 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestTx tests the MsgTx API.
|
||||||
|
func TestTx(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "tx"
|
||||||
|
msg := wire.NewMsgTx()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(1000 * 1000)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction output point data back out.
|
||||||
|
// NOTE: This is a block hash and made up index, but we're only
|
||||||
|
// testing package functionality.
|
||||||
|
prevOutIndex := uint32(1)
|
||||||
|
prevOut := wire.NewOutPoint(hash, prevOutIndex)
|
||||||
|
if !prevOut.Hash.IsEqual(hash) {
|
||||||
|
t.Errorf("NewOutPoint: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(&prevOut.Hash), spew.Sprint(hash))
|
||||||
|
}
|
||||||
|
if prevOut.Index != prevOutIndex {
|
||||||
|
t.Errorf("NewOutPoint: wrong index - got %v, want %v",
|
||||||
|
prevOut.Index, prevOutIndex)
|
||||||
|
}
|
||||||
|
prevOutStr := fmt.Sprintf("%s:%d", hash.String(), prevOutIndex)
|
||||||
|
if s := prevOut.String(); s != prevOutStr {
|
||||||
|
t.Errorf("OutPoint.String: unexpected result - got %v, "+
|
||||||
|
"want %v", s, prevOutStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction input back out.
|
||||||
|
sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}
|
||||||
|
txIn := wire.NewTxIn(prevOut, sigScript)
|
||||||
|
if !reflect.DeepEqual(&txIn.PreviousOutPoint, prevOut) {
|
||||||
|
t.Errorf("NewTxIn: wrong prev outpoint - got %v, want %v",
|
||||||
|
spew.Sprint(&txIn.PreviousOutPoint),
|
||||||
|
spew.Sprint(prevOut))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(txIn.SignatureScript, sigScript) {
|
||||||
|
t.Errorf("NewTxIn: wrong signature script - got %v, want %v",
|
||||||
|
spew.Sdump(txIn.SignatureScript),
|
||||||
|
spew.Sdump(sigScript))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction output back out.
|
||||||
|
txValue := int64(5000000000)
|
||||||
|
pkScript := []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
}
|
||||||
|
txOut := wire.NewTxOut(txValue, pkScript)
|
||||||
|
if txOut.Value != txValue {
|
||||||
|
t.Errorf("NewTxOut: wrong pk script - got %v, want %v",
|
||||||
|
txOut.Value, txValue)
|
||||||
|
|
||||||
|
}
|
||||||
|
if !bytes.Equal(txOut.PkScript, pkScript) {
|
||||||
|
t.Errorf("NewTxOut: wrong pk script - got %v, want %v",
|
||||||
|
spew.Sdump(txOut.PkScript),
|
||||||
|
spew.Sdump(pkScript))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transaction inputs are added properly.
|
||||||
|
msg.AddTxIn(txIn)
|
||||||
|
if !reflect.DeepEqual(msg.TxIn[0], txIn) {
|
||||||
|
t.Errorf("AddTxIn: wrong transaction input added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.TxIn[0]), spew.Sprint(txIn))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transaction outputs are added properly.
|
||||||
|
msg.AddTxOut(txOut)
|
||||||
|
if !reflect.DeepEqual(msg.TxOut[0], txOut) {
|
||||||
|
t.Errorf("AddTxIn: wrong transaction output added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.TxOut[0]), spew.Sprint(txOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the copy produced an identical transaction message.
|
||||||
|
newMsg := msg.Copy()
|
||||||
|
if !reflect.DeepEqual(newMsg, msg) {
|
||||||
|
t.Errorf("Copy: mismatched tx messages - got %v, want %v",
|
||||||
|
spew.Sdump(newMsg), spew.Sdump(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxSha tests the ability to generate the hash of a transaction accurately.
|
||||||
|
func TestTxSha(t *testing.T) {
|
||||||
|
// Hash of first transaction from block 113875.
|
||||||
|
hashStr := "f051e59b5e2503ac626d03aaeac8ab7be2d72ba4b7e97119c5852d70d52dcb86"
|
||||||
|
wantHash, err := wire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First transaction from block 113875.
|
||||||
|
msgTx := wire.NewMsgTx()
|
||||||
|
txIn := wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: wire.ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
}
|
||||||
|
txOut := wire.TxOut{
|
||||||
|
Value: 5000000000,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
}
|
||||||
|
msgTx.AddTxIn(&txIn)
|
||||||
|
msgTx.AddTxOut(&txOut)
|
||||||
|
msgTx.LockTime = 0
|
||||||
|
|
||||||
|
// Ensure the hash produced is expected.
|
||||||
|
txHash, err := msgTx.TxSha()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TxSha: %v", err)
|
||||||
|
}
|
||||||
|
if !txHash.IsEqual(wantHash) {
|
||||||
|
t.Errorf("TxSha: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(txHash), spew.Sprint(wantHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxWire tests the MsgTx wire encode and decode for various numbers
|
||||||
|
// of transaction inputs and outputs and protocol versions.
|
||||||
|
func TestTxWire(t *testing.T) {
|
||||||
|
// Empty tx message.
|
||||||
|
noTx := wire.NewMsgTx()
|
||||||
|
noTx.Version = 1
|
||||||
|
noTxEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x00, // Varint for number of input transactions
|
||||||
|
0x00, // Varint for number of output transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgTx // Message to encode
|
||||||
|
out *wire.MsgTx // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgTx
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgTx to confirm error paths work correctly.
|
||||||
|
func TestTxWireErrors(t *testing.T) {
|
||||||
|
// Use protocol version 60002 specifically here instead of the latest
|
||||||
|
// because the test data is using bytes encoded with that protocol
|
||||||
|
// version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgTx // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in version.
|
||||||
|
{multiTx, multiTxEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in number of transaction inputs.
|
||||||
|
{multiTx, multiTxEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block hash.
|
||||||
|
{multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block hash.
|
||||||
|
{multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block output index.
|
||||||
|
{multiTx, multiTxEncoded, pver, 37, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input signature script length.
|
||||||
|
{multiTx, multiTxEncoded, pver, 41, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input signature script.
|
||||||
|
{multiTx, multiTxEncoded, pver, 42, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input sequence.
|
||||||
|
{multiTx, multiTxEncoded, pver, 49, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in number of transaction outputs.
|
||||||
|
{multiTx, multiTxEncoded, pver, 53, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output value.
|
||||||
|
{multiTx, multiTxEncoded, pver, 54, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output pk script length.
|
||||||
|
{multiTx, multiTxEncoded, pver, 62, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output pk script.
|
||||||
|
{multiTx, multiTxEncoded, pver, 63, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output lock time.
|
||||||
|
{multiTx, multiTxEncoded, pver, 130, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgTx
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = msg.BtcDecode(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxSerialize tests MsgTx serialize and deserialize.
|
||||||
|
func TestTxSerialize(t *testing.T) {
|
||||||
|
noTx := wire.NewMsgTx()
|
||||||
|
noTx.Version = 1
|
||||||
|
noTxEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x00, // Varint for number of input transactions
|
||||||
|
0x00, // Varint for number of output transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgTx // Message to encode
|
||||||
|
out *wire.MsgTx // Expected decoded message
|
||||||
|
buf []byte // Serialized data
|
||||||
|
}{
|
||||||
|
// No transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Serialize the transaction.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.Serialize(&buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Serialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the transaction.
|
||||||
|
var tx wire.MsgTx
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = tx.Deserialize(rbuf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Deserialize #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&tx, test.out) {
|
||||||
|
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&tx), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxSerializeErrors performs negative tests against wire encode and decode
|
||||||
|
// of MsgTx to confirm error paths work correctly.
|
||||||
|
func TestTxSerializeErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgTx // Value to encode
|
||||||
|
buf []byte // Serialized data
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in version.
|
||||||
|
{multiTx, multiTxEncoded, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in number of transaction inputs.
|
||||||
|
{multiTx, multiTxEncoded, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block hash.
|
||||||
|
{multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block hash.
|
||||||
|
{multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input previous block output index.
|
||||||
|
{multiTx, multiTxEncoded, 37, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input signature script length.
|
||||||
|
{multiTx, multiTxEncoded, 41, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input signature script.
|
||||||
|
{multiTx, multiTxEncoded, 42, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction input sequence.
|
||||||
|
{multiTx, multiTxEncoded, 49, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in number of transaction outputs.
|
||||||
|
{multiTx, multiTxEncoded, 53, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output value.
|
||||||
|
{multiTx, multiTxEncoded, 54, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output pk script length.
|
||||||
|
{multiTx, multiTxEncoded, 62, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output pk script.
|
||||||
|
{multiTx, multiTxEncoded, 63, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in transaction output lock time.
|
||||||
|
{multiTx, multiTxEncoded, 130, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Serialize the transaction.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.Serialize(w)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the transaction.
|
||||||
|
var tx wire.MsgTx
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = tx.Deserialize(r)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxOverflowErrors performs tests to ensure deserializing transactions
|
||||||
|
// which are intentionally crafted to use large values for the variable number
|
||||||
|
// of inputs and outputs are handled properly. This could otherwise potentially
|
||||||
|
// be used as an attack vector.
|
||||||
|
func TestTxOverflowErrors(t *testing.T) {
|
||||||
|
// Use protocol version 70001 and transaction version 1 specifically
|
||||||
|
// here instead of the latest values because the test data is using
|
||||||
|
// bytes encoded with those versions.
|
||||||
|
pver := uint32(70001)
|
||||||
|
txVer := uint32(1)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
version uint32 // Transaction version
|
||||||
|
err error // Expected error
|
||||||
|
}{
|
||||||
|
// Transaction that claims to have ~uint64(0) inputs.
|
||||||
|
{
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Version
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, // Varint for number of input transactions
|
||||||
|
}, pver, txVer, &wire.MessageError{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Transaction that claims to have ~uint64(0) outputs.
|
||||||
|
{
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Version
|
||||||
|
0x00, // Varint for number of input transactions
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, // Varint for number of output transactions
|
||||||
|
}, pver, txVer, &wire.MessageError{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Transaction that has an input with a signature script that
|
||||||
|
// claims to have ~uint64(0) length.
|
||||||
|
{
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, // Varint for length of signature script
|
||||||
|
}, pver, txVer, &wire.MessageError{},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Transaction that has an output with a public key script
|
||||||
|
// that claims to have ~uint64(0) length.
|
||||||
|
{
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x00, // Varint for length of signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of output transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, // Varint for length of public key script
|
||||||
|
}, pver, txVer, &wire.MessageError{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgTx
|
||||||
|
r := bytes.NewReader(test.buf)
|
||||||
|
err := msg.BtcDecode(r, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r = bytes.NewReader(test.buf)
|
||||||
|
err = msg.Deserialize(r)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
|
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, reflect.TypeOf(test.err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxSerializeSize performs tests to ensure the serialize size for various
|
||||||
|
// transactions is accurate.
|
||||||
|
func TestTxSerializeSize(t *testing.T) {
|
||||||
|
// Empty tx message.
|
||||||
|
noTx := wire.NewMsgTx()
|
||||||
|
noTx.Version = 1
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgTx // Tx to encode
|
||||||
|
size int // Expected serialized size
|
||||||
|
}{
|
||||||
|
// No inputs or outpus.
|
||||||
|
{noTx, 10},
|
||||||
|
|
||||||
|
// Transcaction with an input and an output.
|
||||||
|
{multiTx, 134},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
serializedSize := test.in.SerializeSize()
|
||||||
|
if serializedSize != test.size {
|
||||||
|
t.Errorf("MsgTx.SerializeSize: #%d got: %d, want: %d", i,
|
||||||
|
serializedSize, test.size)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiTx is a MsgTx with an input and output and used in various tests.
|
||||||
|
var multiTx = &wire.MsgTx{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: wire.ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62,
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiTxEncoded is the wire encoded bytes for multiTx using protocol version
|
||||||
|
// 60002 and is used in the various tests.
|
||||||
|
var multiTxEncoded = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of output transactions
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
46
wire/msgverack.go
Normal file
46
wire/msgverack.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgVerAck defines a bitcoin verack message which is used for a peer to
|
||||||
|
// acknowledge a version message (MsgVersion) after it has used the information
|
||||||
|
// to negotiate parameters. It implements the Message interface.
|
||||||
|
//
|
||||||
|
// This message has no payload.
|
||||||
|
type MsgVerAck struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) Command() string {
|
||||||
|
return CmdVerAck
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVerAck returns a new bitcoin verack message that conforms to the
|
||||||
|
// Message interface.
|
||||||
|
func NewMsgVerAck() *MsgVerAck {
|
||||||
|
return &MsgVerAck{}
|
||||||
|
}
|
122
wire/msgverack_test.go
Normal file
122
wire/msgverack_test.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestVerAck tests the MsgVerAck API.
|
||||||
|
func TestVerAck(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "verack"
|
||||||
|
msg := wire.NewMsgVerAck()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgVerAck: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVerAckWire tests the MsgVerAck wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestVerAckWire(t *testing.T) {
|
||||||
|
msgVerAck := wire.NewMsgVerAck()
|
||||||
|
msgVerAckEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgVerAck // Message to encode
|
||||||
|
out *wire.MsgVerAck // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgVerAck
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
296
wire/msgversion.go
Normal file
296
wire/msgversion.go
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
||||||
|
// version message (MsgVersion).
|
||||||
|
const MaxUserAgentLen = 2000
|
||||||
|
|
||||||
|
// DefaultUserAgent for wire in the stack
|
||||||
|
const DefaultUserAgent = "/btcwire:0.2.0/"
|
||||||
|
|
||||||
|
|
||||||
|
// MsgVersion implements the Message interface and represents a bitcoin version
|
||||||
|
// message. It is used for a peer to advertise itself as soon as an outbound
|
||||||
|
// connection is made. The remote peer then uses this information along with
|
||||||
|
// its own to negotiate. The remote peer must then respond with a version
|
||||||
|
// message of its own containing the negotiated values followed by a verack
|
||||||
|
// message (MsgVerAck). This exchange must take place before any further
|
||||||
|
// communication is allowed to proceed.
|
||||||
|
type MsgVersion struct {
|
||||||
|
// Version of the protocol the node is using.
|
||||||
|
ProtocolVersion int32
|
||||||
|
|
||||||
|
// Bitfield which identifies the enabled services.
|
||||||
|
Services ServiceFlag
|
||||||
|
|
||||||
|
// Time the message was generated. This is encoded as an int64 on the wire.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Address of the remote peer.
|
||||||
|
AddrYou NetAddress
|
||||||
|
|
||||||
|
// Address of the local peer.
|
||||||
|
AddrMe NetAddress
|
||||||
|
|
||||||
|
// Unique value associated with message that is used to detect self
|
||||||
|
// connections.
|
||||||
|
Nonce uint64
|
||||||
|
|
||||||
|
// The user agent that generated messsage. This is a encoded as a varString
|
||||||
|
// on the wire. This has a max length of MaxUserAgentLen.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Last block seen by the generator of the version message.
|
||||||
|
LastBlock int32
|
||||||
|
|
||||||
|
// Don't announce transactions to peer.
|
||||||
|
DisableRelayTx bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasService returns whether the specified service is supported by the peer
|
||||||
|
// that generated the message.
|
||||||
|
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
|
||||||
|
if msg.Services&service == service {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddService adds service as a supported service by the peer generating the
|
||||||
|
// message.
|
||||||
|
func (msg *MsgVersion) AddService(service ServiceFlag) {
|
||||||
|
msg.Services |= service
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// The version message is special in that the protocol version hasn't been
|
||||||
|
// negotiated yet. As a result, the pver field is ignored and any fields which
|
||||||
|
// are added in new versions are optional. This also mean that r must be a
|
||||||
|
// *bytes.Buffer so the number of remaining bytes can be ascertained.
|
||||||
|
//
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
buf, ok := r.(*bytes.Buffer)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("MsgVersion.BtcDecode reader is not a " +
|
||||||
|
"*bytes.Buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sec int64
|
||||||
|
err := readElements(buf, &msg.ProtocolVersion, &msg.Services, &sec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Timestamp = time.Unix(sec, 0)
|
||||||
|
|
||||||
|
err = readNetAddress(buf, pver, &msg.AddrYou, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol versions >= 106 added a from address, nonce, and user agent
|
||||||
|
// field and they are only considered present if there are bytes
|
||||||
|
// remaining in the message.
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
err = readNetAddress(buf, pver, &msg.AddrMe, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
err = readElement(buf, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
userAgent, err := readVarString(buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = validateUserAgent(userAgent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.UserAgent = userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol versions >= 209 added a last known block field. It is only
|
||||||
|
// considered present if there are bytes remaining in the message.
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
err = readElement(buf, &msg.LastBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was no relay transactions field before BIP0037Version, but
|
||||||
|
// the default behavior prior to the addition of the field was to always
|
||||||
|
// relay transactions.
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
// It's safe to ignore the error here since the buffer has at
|
||||||
|
// least one byte and that byte will result in a boolean value
|
||||||
|
// regardless of its value. Also, the wire encoding for the
|
||||||
|
// field is true when transactions should be relayed, so reverse
|
||||||
|
// it for the DisableRelayTx field.
|
||||||
|
var relayTx bool
|
||||||
|
readElement(r, &relayTx)
|
||||||
|
msg.DisableRelayTx = !relayTx
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
err := validateUserAgent(msg.UserAgent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElements(w, msg.ProtocolVersion, msg.Services,
|
||||||
|
msg.Timestamp.Unix())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeNetAddress(w, pver, &msg.AddrYou, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeNetAddress(w, pver, &msg.AddrMe, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarString(w, pver, msg.UserAgent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.LastBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was no relay transactions field before BIP0037Version. Also,
|
||||||
|
// the wire encoding for the field is true when transactions should be
|
||||||
|
// relayed, so reverse it from the DisableRelayTx field.
|
||||||
|
if pver >= BIP0037Version {
|
||||||
|
err = writeElement(w, !msg.DisableRelayTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) Command() string {
|
||||||
|
return CmdVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// XXX: <= 106 different
|
||||||
|
|
||||||
|
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes +
|
||||||
|
// remote and local net addresses + nonce 8 bytes + length of user
|
||||||
|
// agent (varInt) + max allowed useragent length + last block 4 bytes +
|
||||||
|
// relay transactions flag 1 byte.
|
||||||
|
return 33 + (maxNetAddressPayload(pver) * 2) + MaxVarIntPayload +
|
||||||
|
MaxUserAgentLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVersion returns a new bitcoin version message that conforms to the
|
||||||
|
// Message interface using the passed parameters and defaults for the remaining
|
||||||
|
// fields.
|
||||||
|
func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64,
|
||||||
|
lastBlock int32) *MsgVersion {
|
||||||
|
|
||||||
|
// Limit the timestamp to one second precision since the protocol
|
||||||
|
// doesn't support better.
|
||||||
|
return &MsgVersion{
|
||||||
|
ProtocolVersion: int32(ProtocolVersion),
|
||||||
|
Services: 0,
|
||||||
|
Timestamp: time.Unix(time.Now().Unix(), 0),
|
||||||
|
AddrYou: *you,
|
||||||
|
AddrMe: *me,
|
||||||
|
Nonce: nonce,
|
||||||
|
UserAgent: DefaultUserAgent,
|
||||||
|
LastBlock: lastBlock,
|
||||||
|
DisableRelayTx: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVersionFromConn is a convenience function that extracts the remote
|
||||||
|
// and local address from conn and returns a new bitcoin version message that
|
||||||
|
// conforms to the Message interface. See NewMsgVersion.
|
||||||
|
func NewMsgVersionFromConn(conn net.Conn, nonce uint64,
|
||||||
|
lastBlock int32) (*MsgVersion, error) {
|
||||||
|
|
||||||
|
// Don't assume any services until we know otherwise.
|
||||||
|
lna, err := NewNetAddress(conn.LocalAddr(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't assume any services until we know otherwise.
|
||||||
|
rna, err := NewNetAddress(conn.RemoteAddr(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewMsgVersion(lna, rna, nonce, lastBlock), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateUserAgent checks userAgent length against MaxUserAgentLen
|
||||||
|
func validateUserAgent(userAgent string) error {
|
||||||
|
if len(userAgent) > MaxUserAgentLen {
|
||||||
|
str := fmt.Sprintf("user agent too long [len %v, max %v]",
|
||||||
|
len(userAgent), MaxUserAgentLen)
|
||||||
|
return messageError("MsgVersion", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgent adds a user agent to the user agent string for the version
|
||||||
|
// message. The version string is not defined to any strict format, although
|
||||||
|
// it is recommended to use the form "major.minor.revision" e.g. "2.6.41".
|
||||||
|
func (msg *MsgVersion) AddUserAgent(name string, version string,
|
||||||
|
comments ...string) error {
|
||||||
|
|
||||||
|
newUserAgent := fmt.Sprintf("%s:%s", name, version)
|
||||||
|
if len(comments) != 0 {
|
||||||
|
newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent,
|
||||||
|
strings.Join(comments, "; "))
|
||||||
|
}
|
||||||
|
newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent)
|
||||||
|
err := validateUserAgent(newUserAgent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.UserAgent = newUserAgent
|
||||||
|
return nil
|
||||||
|
}
|
596
wire/msgversion_test.go
Normal file
596
wire/msgversion_test.go
Normal file
|
@ -0,0 +1,596 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestVersion tests the MsgVersion API.
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
|
||||||
|
// Create version message data.
|
||||||
|
lastBlock := int32(234234)
|
||||||
|
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
me, err := wire.NewNetAddress(tcpAddrMe, wire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
tcpAddrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}
|
||||||
|
you, err := wire.NewNetAddress(tcpAddrYou, wire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
nonce, err := wire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the correct data back out.
|
||||||
|
msg := wire.NewMsgVersion(me, you, nonce, lastBlock)
|
||||||
|
if msg.ProtocolVersion != int32(pver) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
||||||
|
msg.ProtocolVersion, pver)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg.AddrMe, me) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong me address - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.AddrMe), spew.Sdump(me))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg.AddrYou, you) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong you address - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.AddrYou), spew.Sdump(you))
|
||||||
|
}
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgVersion: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
if msg.UserAgent != wire.DefaultUserAgent {
|
||||||
|
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
||||||
|
msg.UserAgent, wire.DefaultUserAgent)
|
||||||
|
}
|
||||||
|
if msg.LastBlock != lastBlock {
|
||||||
|
t.Errorf("NewMsgVersion: wrong last block - got %v, want %v",
|
||||||
|
msg.LastBlock, lastBlock)
|
||||||
|
}
|
||||||
|
if msg.DisableRelayTx != false {
|
||||||
|
t.Errorf("NewMsgVersion: disable relay tx is not false by "+
|
||||||
|
"default - got %v, want %v", msg.DisableRelayTx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddUserAgent("myclient", "1.2.3", "optional", "comments")
|
||||||
|
customUserAgent := wire.DefaultUserAgent + "myclient:1.2.3(optional; comments)/"
|
||||||
|
if msg.UserAgent != customUserAgent {
|
||||||
|
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
||||||
|
msg.UserAgent, customUserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddUserAgent("mygui", "3.4.5")
|
||||||
|
customUserAgent += "mygui:3.4.5/"
|
||||||
|
if msg.UserAgent != customUserAgent {
|
||||||
|
t.Errorf("AddUserAgent: wrong user agent - got %s, want %s",
|
||||||
|
msg.UserAgent, customUserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accounting for ":", "/"
|
||||||
|
err = msg.AddUserAgent(strings.Repeat("t",
|
||||||
|
wire.MaxUserAgentLen-len(customUserAgent)-2+1), "")
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
t.Errorf("AddUserAgent: expected error not received "+
|
||||||
|
"- got %v, want %T", err, wire.MessageError{})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version message should not have any services set by default.
|
||||||
|
if msg.Services != 0 {
|
||||||
|
t.Errorf("NewMsgVersion: wrong default services - got %v, want %v",
|
||||||
|
msg.Services, 0)
|
||||||
|
|
||||||
|
}
|
||||||
|
if msg.HasService(wire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "version"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgVersion: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes +
|
||||||
|
// remote and local net addresses + nonce 8 bytes + length of user agent
|
||||||
|
// (varInt) + max allowed user agent length + last block 4 bytes +
|
||||||
|
// relay transactions flag 1 byte.
|
||||||
|
wantPayload := uint32(2102)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding the full service node flag works.
|
||||||
|
msg.AddService(wire.SFNodeNetwork)
|
||||||
|
if msg.Services != wire.SFNodeNetwork {
|
||||||
|
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||||
|
msg.Services, wire.SFNodeNetwork)
|
||||||
|
}
|
||||||
|
if !msg.HasService(wire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection.
|
||||||
|
conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou}
|
||||||
|
msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the correct connection data back out.
|
||||||
|
if !msg.AddrMe.IP.Equal(tcpAddrMe.IP) {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: wrong me ip - got %v, want %v",
|
||||||
|
msg.AddrMe.IP, tcpAddrMe.IP)
|
||||||
|
}
|
||||||
|
if !msg.AddrYou.IP.Equal(tcpAddrYou.IP) {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: wrong you ip - got %v, want %v",
|
||||||
|
msg.AddrYou.IP, tcpAddrYou.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection with local UDP addresses to force a failure.
|
||||||
|
conn = &fakeConn{
|
||||||
|
localAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333},
|
||||||
|
remoteAddr: tcpAddrYou,
|
||||||
|
}
|
||||||
|
msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock)
|
||||||
|
if err != wire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: expected error not received "+
|
||||||
|
"- got %v, want %v", err, wire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection with remote UDP addresses to force a failure.
|
||||||
|
conn = &fakeConn{
|
||||||
|
localAddr: tcpAddrMe,
|
||||||
|
remoteAddr: &net.UDPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333},
|
||||||
|
}
|
||||||
|
msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock)
|
||||||
|
if err != wire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: expected error not received "+
|
||||||
|
"- got %v, want %v", err, wire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVersionWire tests the MsgVersion wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestVersionWire(t *testing.T) {
|
||||||
|
// verRelayTxFalse and verRelayTxFalseEncoded is a version message as of
|
||||||
|
// BIP0037Version with the transaction relay disabled.
|
||||||
|
baseVersionBIP0037Copy := *baseVersionBIP0037
|
||||||
|
verRelayTxFalse := &baseVersionBIP0037Copy
|
||||||
|
verRelayTxFalse.DisableRelayTx = true
|
||||||
|
verRelayTxFalseEncoded := make([]byte, len(baseVersionBIP0037Encoded))
|
||||||
|
copy(verRelayTxFalseEncoded, baseVersionBIP0037Encoded)
|
||||||
|
verRelayTxFalseEncoded[len(verRelayTxFalseEncoded)-1] = 0
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgVersion // Message to encode
|
||||||
|
out *wire.MsgVersion // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseVersionBIP0037,
|
||||||
|
baseVersionBIP0037,
|
||||||
|
baseVersionBIP0037Encoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0037Version with relay transactions field
|
||||||
|
// true.
|
||||||
|
{
|
||||||
|
baseVersionBIP0037,
|
||||||
|
baseVersionBIP0037,
|
||||||
|
baseVersionBIP0037Encoded,
|
||||||
|
wire.BIP0037Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0037Version with relay transactions field
|
||||||
|
// false.
|
||||||
|
{
|
||||||
|
verRelayTxFalse,
|
||||||
|
verRelayTxFalse,
|
||||||
|
verRelayTxFalseEncoded,
|
||||||
|
wire.BIP0037Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
wire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
wire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
wire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgVersion
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVersionWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of MsgGetHeaders to confirm error paths work correctly.
|
||||||
|
func TestVersionWireErrors(t *testing.T) {
|
||||||
|
// Use protocol version 60002 specifically here instead of the latest
|
||||||
|
// because the test data is using bytes encoded with that protocol
|
||||||
|
// version.
|
||||||
|
pver := uint32(60002)
|
||||||
|
wireErr := &wire.MessageError{}
|
||||||
|
|
||||||
|
// Ensure calling MsgVersion.BtcDecode with a non *bytes.Buffer returns
|
||||||
|
// error.
|
||||||
|
fr := newFixedReader(0, []byte{})
|
||||||
|
if err := baseVersion.BtcDecode(fr, pver); err == nil {
|
||||||
|
t.Errorf("Did not received error when calling " +
|
||||||
|
"MsgVersion.BtcDecode with non *bytes.Buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the base version and change the user agent to exceed max limits.
|
||||||
|
bvc := *baseVersion
|
||||||
|
exceedUAVer := &bvc
|
||||||
|
newUA := "/" + strings.Repeat("t", wire.MaxUserAgentLen-8+1) + ":0.0.1/"
|
||||||
|
exceedUAVer.UserAgent = newUA
|
||||||
|
|
||||||
|
// Encode the new UA length as a varint.
|
||||||
|
var newUAVarIntBuf bytes.Buffer
|
||||||
|
err := wire.TstWriteVarInt(&newUAVarIntBuf, pver, uint64(len(newUA)))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarInt: error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new buffer big enough to hold the base version plus the new
|
||||||
|
// bytes for the bigger varint to hold the new size of the user agent
|
||||||
|
// and the new user agent string. Then stich it all together.
|
||||||
|
newLen := len(baseVersionEncoded) - len(baseVersion.UserAgent)
|
||||||
|
newLen = newLen + len(newUAVarIntBuf.Bytes()) - 1 + len(newUA)
|
||||||
|
exceedUAVerEncoded := make([]byte, newLen)
|
||||||
|
copy(exceedUAVerEncoded, baseVersionEncoded[0:80])
|
||||||
|
copy(exceedUAVerEncoded[80:], newUAVarIntBuf.Bytes())
|
||||||
|
copy(exceedUAVerEncoded[83:], []byte(newUA))
|
||||||
|
copy(exceedUAVerEncoded[83+len(newUA):], baseVersionEncoded[97:100])
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.MsgVersion // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Force error in protocol version.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in services.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in timestamp.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 12, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in remote address.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 20, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in local address.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 47, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force error in nonce.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 73, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force error in user agent length.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 81, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error in user agent.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 82, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force error in last block.
|
||||||
|
{baseVersion, baseVersionEncoded, pver, 98, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force error in relay tx - no read error should happen since
|
||||||
|
// it's optional.
|
||||||
|
{
|
||||||
|
baseVersionBIP0037, baseVersionBIP0037Encoded,
|
||||||
|
wire.BIP0037Version, 101, io.ErrShortWrite, nil,
|
||||||
|
},
|
||||||
|
// Force error due to user agent too big
|
||||||
|
{exceedUAVer, exceedUAVerEncoded, pver, newLen, wireErr, wireErr},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := test.in.BtcEncode(w, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("BtcEncode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var msg wire.MsgVersion
|
||||||
|
buf := bytes.NewBuffer(test.buf[0:test.max])
|
||||||
|
err = msg.BtcDecode(buf, test.pver)
|
||||||
|
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For errors which are not of type wire.MessageError, check
|
||||||
|
// them for equality.
|
||||||
|
if _, ok := err.(*wire.MessageError); !ok {
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("BtcDecode #%d wrong error got: %v, "+
|
||||||
|
"want: %v", i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVersionOptionalFields performs tests to ensure that an encoded version
|
||||||
|
// messages that omit optional fields are handled correctly.
|
||||||
|
func TestVersionOptionalFields(t *testing.T) {
|
||||||
|
// onlyRequiredVersion is a version message that only contains the
|
||||||
|
// required versions and all other values set to their default values.
|
||||||
|
onlyRequiredVersion := wire.MsgVersion{
|
||||||
|
ProtocolVersion: 60002,
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST)
|
||||||
|
AddrYou: wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
onlyRequiredVersionEncoded := make([]byte, len(baseVersionEncoded)-55)
|
||||||
|
copy(onlyRequiredVersionEncoded, baseVersionEncoded)
|
||||||
|
|
||||||
|
// addrMeVersion is a version message that contains all fields through
|
||||||
|
// the AddrMe field.
|
||||||
|
addrMeVersion := onlyRequiredVersion
|
||||||
|
addrMeVersion.AddrMe = wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
addrMeVersionEncoded := make([]byte, len(baseVersionEncoded)-29)
|
||||||
|
copy(addrMeVersionEncoded, baseVersionEncoded)
|
||||||
|
|
||||||
|
// nonceVersion is a version message that contains all fields through
|
||||||
|
// the Nonce field.
|
||||||
|
nonceVersion := addrMeVersion
|
||||||
|
nonceVersion.Nonce = 123123 // 0x1e0f3
|
||||||
|
nonceVersionEncoded := make([]byte, len(baseVersionEncoded)-21)
|
||||||
|
copy(nonceVersionEncoded, baseVersionEncoded)
|
||||||
|
|
||||||
|
// uaVersion is a version message that contains all fields through
|
||||||
|
// the UserAgent field.
|
||||||
|
uaVersion := nonceVersion
|
||||||
|
uaVersion.UserAgent = "/btcdtest:0.0.1/"
|
||||||
|
uaVersionEncoded := make([]byte, len(baseVersionEncoded)-4)
|
||||||
|
copy(uaVersionEncoded, baseVersionEncoded)
|
||||||
|
|
||||||
|
// lastBlockVersion is a version message that contains all fields
|
||||||
|
// through the LastBlock field.
|
||||||
|
lastBlockVersion := uaVersion
|
||||||
|
lastBlockVersion.LastBlock = 234234 // 0x392fa
|
||||||
|
lastBlockVersionEncoded := make([]byte, len(baseVersionEncoded))
|
||||||
|
copy(lastBlockVersionEncoded, baseVersionEncoded)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
msg *wire.MsgVersion // Expected message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&onlyRequiredVersion,
|
||||||
|
onlyRequiredVersionEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&addrMeVersion,
|
||||||
|
addrMeVersionEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&nonceVersion,
|
||||||
|
nonceVersionEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&uaVersion,
|
||||||
|
uaVersionEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&lastBlockVersion,
|
||||||
|
lastBlockVersionEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg wire.MsgVersion
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err := msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.msg) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.msg))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseVersion is used in the various tests as a baseline MsgVersion.
|
||||||
|
var baseVersion = &wire.MsgVersion{
|
||||||
|
ProtocolVersion: 60002,
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST)
|
||||||
|
AddrYou: wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
},
|
||||||
|
AddrMe: wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
},
|
||||||
|
Nonce: 123123, // 0x1e0f3
|
||||||
|
UserAgent: "/btcdtest:0.0.1/",
|
||||||
|
LastBlock: 234234, // 0x392fa
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseVersionEncoded is the wire encoded bytes for baseVersion using protocol
|
||||||
|
// version 60002 and is used in the various tests.
|
||||||
|
var baseVersionEncoded = []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // 64-bit Timestamp
|
||||||
|
// AddrYou -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
// AddrMe -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Nonce
|
||||||
|
0x10, // Varint for user agent length
|
||||||
|
0x2f, 0x62, 0x74, 0x63, 0x64, 0x74, 0x65, 0x73,
|
||||||
|
0x74, 0x3a, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f, // User agent
|
||||||
|
0xfa, 0x92, 0x03, 0x00, // Last block
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseVersionBIP0037 is used in the various tests as a baseline MsgVersion for
|
||||||
|
// BIP0037.
|
||||||
|
var baseVersionBIP0037 = &wire.MsgVersion{
|
||||||
|
ProtocolVersion: 70001,
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST)
|
||||||
|
AddrYou: wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
},
|
||||||
|
AddrMe: wire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
},
|
||||||
|
Nonce: 123123, // 0x1e0f3
|
||||||
|
UserAgent: "/btcdtest:0.0.1/",
|
||||||
|
LastBlock: 234234, // 0x392fa
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseVersionBIP0037Encoded is the wire encoded bytes for baseVersionBIP0037
|
||||||
|
// using protocol version BIP0037Version and is used in the various tests.
|
||||||
|
var baseVersionBIP0037Encoded = []byte{
|
||||||
|
0x71, 0x11, 0x01, 0x00, // Protocol version 70001
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // 64-bit Timestamp
|
||||||
|
// AddrYou -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
// AddrMe -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Nonce
|
||||||
|
0x10, // Varint for user agent length
|
||||||
|
0x2f, 0x62, 0x74, 0x63, 0x64, 0x74, 0x65, 0x73,
|
||||||
|
0x74, 0x3a, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f, // User agent
|
||||||
|
0xfa, 0x92, 0x03, 0x00, // Last block
|
||||||
|
0x01, // Relay tx
|
||||||
|
}
|
172
wire/netaddress.go
Normal file
172
wire/netaddress.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidNetAddr describes an error that indicates the caller didn't specify
|
||||||
|
// a TCP address as required.
|
||||||
|
var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr")
|
||||||
|
|
||||||
|
// maxNetAddressPayload returns the max payload size for a bitcoin NetAddress
|
||||||
|
// based on the protocol version.
|
||||||
|
func maxNetAddressPayload(pver uint32) uint32 {
|
||||||
|
// Services 8 bytes + ip 16 bytes + port 2 bytes.
|
||||||
|
plen := uint32(26)
|
||||||
|
|
||||||
|
// NetAddressTimeVersion added a timestamp field.
|
||||||
|
if pver >= NetAddressTimeVersion {
|
||||||
|
// Timestamp 4 bytes.
|
||||||
|
plen += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetAddress defines information about a peer on the network including the time
|
||||||
|
// it was last seen, the services it supports, its IP address, and port.
|
||||||
|
type NetAddress struct {
|
||||||
|
// Last time the address was seen. This is, unfortunately, encoded as a
|
||||||
|
// uint32 on the wire and therefore is limited to 2106. This field is
|
||||||
|
// not present in the bitcoin version message (MsgVersion) nor was it
|
||||||
|
// added until protocol version >= NetAddressTimeVersion.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Bitfield which identifies the services supported by the address.
|
||||||
|
Services ServiceFlag
|
||||||
|
|
||||||
|
// IP address of the peer.
|
||||||
|
IP net.IP
|
||||||
|
|
||||||
|
// Port the peer is using. This is encoded in big endian on the wire
|
||||||
|
// which differs from most everything else.
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasService returns whether the specified service is supported by the address.
|
||||||
|
func (na *NetAddress) HasService(service ServiceFlag) bool {
|
||||||
|
if na.Services&service == service {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddService adds service as a supported service by the peer generating the
|
||||||
|
// message.
|
||||||
|
func (na *NetAddress) AddService(service ServiceFlag) {
|
||||||
|
na.Services |= service
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddress is a convenience function to set the IP address and port in one
|
||||||
|
// call.
|
||||||
|
func (na *NetAddress) SetAddress(ip net.IP, port uint16) {
|
||||||
|
na.IP = ip
|
||||||
|
na.Port = port
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetAddressIPPort returns a new NetAddress using the provided IP, port, and
|
||||||
|
// supported services with defaults for the remaining fields.
|
||||||
|
func NewNetAddressIPPort(ip net.IP, port uint16, services ServiceFlag) *NetAddress {
|
||||||
|
// Limit the timestamp to one second precision since the protocol
|
||||||
|
// doesn't support better.
|
||||||
|
na := NetAddress{
|
||||||
|
Timestamp: time.Unix(time.Now().Unix(), 0),
|
||||||
|
Services: services,
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
return &na
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetAddress returns a new NetAddress using the provided TCP address and
|
||||||
|
// supported services with defaults for the remaining fields.
|
||||||
|
//
|
||||||
|
// Note that addr must be a net.TCPAddr. An ErrInvalidNetAddr is returned
|
||||||
|
// if it is not.
|
||||||
|
func NewNetAddress(addr net.Addr, services ServiceFlag) (*NetAddress, error) {
|
||||||
|
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrInvalidNetAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
na := NewNetAddressIPPort(tcpAddr.IP, uint16(tcpAddr.Port), services)
|
||||||
|
return na, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readNetAddress reads an encoded NetAddress from r depending on the protocol
|
||||||
|
// version and whether or not the timestamp is included per ts. Some messages
|
||||||
|
// like version do not include the timestamp.
|
||||||
|
func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
var timestamp time.Time
|
||||||
|
var services ServiceFlag
|
||||||
|
var ip [16]byte
|
||||||
|
var port uint16
|
||||||
|
|
||||||
|
// NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will
|
||||||
|
// stop working somewhere around 2106. Also timestamp wasn't added until
|
||||||
|
// protocol version >= NetAddressTimeVersion
|
||||||
|
if ts && pver >= NetAddressTimeVersion {
|
||||||
|
var stamp uint32
|
||||||
|
err := readElement(r, &stamp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp = time.Unix(int64(stamp), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := readElements(r, &services, &ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Sigh. Bitcoin protocol mixes little and big endian.
|
||||||
|
err = binary.Read(r, binary.BigEndian, &port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
na.Timestamp = timestamp
|
||||||
|
na.Services = services
|
||||||
|
na.SetAddress(net.IP(ip[:]), port)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeNetAddress serializes a NetAddress to w depending on the protocol
|
||||||
|
// version and whether or not the timestamp is included per ts. Some messages
|
||||||
|
// like version do not include the timestamp.
|
||||||
|
func writeNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
// NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will
|
||||||
|
// stop working somewhere around 2106. Also timestamp wasn't added until
|
||||||
|
// until protocol version >= NetAddressTimeVersion.
|
||||||
|
if ts && pver >= NetAddressTimeVersion {
|
||||||
|
err := writeElement(w, uint32(na.Timestamp.Unix()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure to always write 16 bytes even if the ip is nil.
|
||||||
|
var ip [16]byte
|
||||||
|
if na.IP != nil {
|
||||||
|
copy(ip[:], na.IP.To16())
|
||||||
|
}
|
||||||
|
err := writeElements(w, na.Services, ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigh. Bitcoin protocol mixes little and big endian.
|
||||||
|
err = binary.Write(w, binary.BigEndian, na.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
294
wire/netaddress_test.go
Normal file
294
wire/netaddress_test.go
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNetAddress tests the NetAddress API.
|
||||||
|
func TestNetAddress(t *testing.T) {
|
||||||
|
ip := net.ParseIP("127.0.0.1")
|
||||||
|
port := 8333
|
||||||
|
|
||||||
|
// Test NewNetAddress.
|
||||||
|
tcpAddr := &net.TCPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
na, err := wire.NewNetAddress(tcpAddr, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same ip, port, and services back out.
|
||||||
|
if !na.IP.Equal(ip) {
|
||||||
|
t.Errorf("NetNetAddress: wrong ip - got %v, want %v", na.IP, ip)
|
||||||
|
}
|
||||||
|
if na.Port != uint16(port) {
|
||||||
|
t.Errorf("NetNetAddress: wrong port - got %v, want %v", na.Port,
|
||||||
|
port)
|
||||||
|
}
|
||||||
|
if na.Services != 0 {
|
||||||
|
t.Errorf("NetNetAddress: wrong services - got %v, want %v",
|
||||||
|
na.Services, 0)
|
||||||
|
}
|
||||||
|
if na.HasService(wire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding the full service node flag works.
|
||||||
|
na.AddService(wire.SFNodeNetwork)
|
||||||
|
if na.Services != wire.SFNodeNetwork {
|
||||||
|
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||||
|
na.Services, wire.SFNodeNetwork)
|
||||||
|
}
|
||||||
|
if !na.HasService(wire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
wantPayload := uint32(30)
|
||||||
|
maxPayload := wire.TstMaxNetAddressPayload(wire.ProtocolVersion)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("maxNetAddressPayload: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol version before NetAddressTimeVersion when timestamp was
|
||||||
|
// added. Ensure max payload is expected value for it.
|
||||||
|
pver = wire.NetAddressTimeVersion - 1
|
||||||
|
wantPayload = 26
|
||||||
|
maxPayload = wire.TstMaxNetAddressPayload(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("maxNetAddressPayload: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for expected failure on wrong address type.
|
||||||
|
udpAddr := &net.UDPAddr{}
|
||||||
|
_, err = wire.NewNetAddress(udpAddr, 0)
|
||||||
|
if err != wire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewNetAddress: expected error not received - "+
|
||||||
|
"got %v, want %v", err, wire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNetAddressWire tests the NetAddress wire encode and decode for various
|
||||||
|
// protocol versions and timestamp flag combinations.
|
||||||
|
func TestNetAddressWire(t *testing.T) {
|
||||||
|
// baseNetAddr is used in the various tests as a baseline NetAddress.
|
||||||
|
baseNetAddr := wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNetAddrNoTS is baseNetAddr with a zero value for the timestamp.
|
||||||
|
baseNetAddrNoTS := baseNetAddr
|
||||||
|
baseNetAddrNoTS.Timestamp = time.Time{}
|
||||||
|
|
||||||
|
// baseNetAddrEncoded is the wire encoded bytes of baseNetAddr.
|
||||||
|
baseNetAddrEncoded := []byte{
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNetAddrNoTSEncoded is the wire encoded bytes of baseNetAddrNoTS.
|
||||||
|
baseNetAddrNoTSEncoded := []byte{
|
||||||
|
// No timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in wire.NetAddress // NetAddress to encode
|
||||||
|
out wire.NetAddress // Expected decoded NetAddress
|
||||||
|
ts bool // Include timestamp?
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddr,
|
||||||
|
true,
|
||||||
|
baseNetAddrEncoded,
|
||||||
|
wire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddr,
|
||||||
|
true,
|
||||||
|
baseNetAddrEncoded,
|
||||||
|
wire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion-1 without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
wire.NetAddressTimeVersion - 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion-1 with timestamp.
|
||||||
|
// Even though the timestamp flag is set, this shouldn't have a
|
||||||
|
// timestamp since it is a protocol version before it was
|
||||||
|
// added.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
true,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
wire.NetAddressTimeVersion - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := wire.TstWriteNetAddress(&buf, test.pver, &test.in, test.ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeNetAddress #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeNetAddress #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var na wire.NetAddress
|
||||||
|
rbuf := bytes.NewReader(test.buf)
|
||||||
|
err = wire.TstReadNetAddress(rbuf, test.pver, &na, test.ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readNetAddress #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(na, test.out) {
|
||||||
|
t.Errorf("readNetAddress #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(na), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNetAddressWireErrors performs negative tests against wire encode and
|
||||||
|
// decode NetAddress to confirm error paths work correctly.
|
||||||
|
func TestNetAddressWireErrors(t *testing.T) {
|
||||||
|
pver := wire.ProtocolVersion
|
||||||
|
pverNAT := wire.NetAddressTimeVersion - 1
|
||||||
|
|
||||||
|
// baseNetAddr is used in the various tests as a baseline NetAddress.
|
||||||
|
baseNetAddr := wire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: wire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *wire.NetAddress // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
ts bool // Include timestamp flag
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with timestamp and intentional
|
||||||
|
// read/write errors.
|
||||||
|
// Force errors on timestamp.
|
||||||
|
{&baseNetAddr, []byte{}, pver, true, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on services.
|
||||||
|
{&baseNetAddr, []byte{}, pver, true, 4, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on ip.
|
||||||
|
{&baseNetAddr, []byte{}, pver, true, 12, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on port.
|
||||||
|
{&baseNetAddr, []byte{}, pver, true, 28, io.ErrShortWrite, io.EOF},
|
||||||
|
|
||||||
|
// Latest protocol version with no timestamp and intentional
|
||||||
|
// read/write errors.
|
||||||
|
// Force errors on services.
|
||||||
|
{&baseNetAddr, []byte{}, pver, false, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on ip.
|
||||||
|
{&baseNetAddr, []byte{}, pver, false, 8, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on port.
|
||||||
|
{&baseNetAddr, []byte{}, pver, false, 24, io.ErrShortWrite, io.EOF},
|
||||||
|
|
||||||
|
// Protocol version before NetAddressTimeVersion with timestamp
|
||||||
|
// flag set (should not have timestamp due to old protocol
|
||||||
|
// version) and intentional read/write errors.
|
||||||
|
// Force errors on services.
|
||||||
|
{&baseNetAddr, []byte{}, pverNAT, true, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on ip.
|
||||||
|
{&baseNetAddr, []byte{}, pverNAT, true, 8, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on port.
|
||||||
|
{&baseNetAddr, []byte{}, pverNAT, true, 24, io.ErrShortWrite, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := wire.TstWriteNetAddress(w, test.pver, test.in, test.ts)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeNetAddress #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
var na wire.NetAddress
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
err = wire.TstReadNetAddress(r, test.pver, &na, test.ts)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readNetAddress #%d wrong error got: %v, want: %v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
wire/protocol.go
Normal file
118
wire/protocol.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ProtocolVersion is the latest protocol version this package supports.
|
||||||
|
ProtocolVersion uint32 = 70002
|
||||||
|
|
||||||
|
// MultipleAddressVersion is the protocol version which added multiple
|
||||||
|
// addresses per message (pver >= MultipleAddressVersion).
|
||||||
|
MultipleAddressVersion uint32 = 209
|
||||||
|
|
||||||
|
// NetAddressTimeVersion is the protocol version which added the
|
||||||
|
// timestamp field (pver >= NetAddressTimeVersion).
|
||||||
|
NetAddressTimeVersion uint32 = 31402
|
||||||
|
|
||||||
|
// BIP0031Version is the protocol version AFTER which a pong message
|
||||||
|
// and nonce field in ping were added (pver > BIP0031Version).
|
||||||
|
BIP0031Version uint32 = 60000
|
||||||
|
|
||||||
|
// BIP0035Version is the protocol version which added the mempool
|
||||||
|
// message (pver >= BIP0035Version).
|
||||||
|
BIP0035Version uint32 = 60002
|
||||||
|
|
||||||
|
// BIP0037Version is the protocol version which added new connection
|
||||||
|
// bloom filtering related messages and extended the version message
|
||||||
|
// with a relay flag (pver >= BIP0037Version).
|
||||||
|
BIP0037Version uint32 = 70001
|
||||||
|
|
||||||
|
// RejectVersion is the protocol version which added a new reject
|
||||||
|
// message.
|
||||||
|
RejectVersion uint32 = 70002
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceFlag identifies services supported by a bitcoin peer.
|
||||||
|
type ServiceFlag uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SFNodeNetwork is a flag used to indicate a peer is a full node.
|
||||||
|
SFNodeNetwork ServiceFlag = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of service flags back to their constant names for pretty printing.
|
||||||
|
var sfStrings = map[ServiceFlag]string{
|
||||||
|
SFNodeNetwork: "SFNodeNetwork",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the ServiceFlag in human-readable form.
|
||||||
|
func (f ServiceFlag) String() string {
|
||||||
|
// No flags are set.
|
||||||
|
if f == 0 {
|
||||||
|
return "0x0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add individual bit flags.
|
||||||
|
s := ""
|
||||||
|
for flag, name := range sfStrings {
|
||||||
|
if f&flag == flag {
|
||||||
|
s += name + "|"
|
||||||
|
f -= flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining flags which aren't accounted for as hex.
|
||||||
|
s = strings.TrimRight(s, "|")
|
||||||
|
if f != 0 {
|
||||||
|
s += "|0x" + strconv.FormatUint(uint64(f), 16)
|
||||||
|
}
|
||||||
|
s = strings.TrimLeft(s, "|")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitcoinNet represents which bitcoin network a message belongs to.
|
||||||
|
type BitcoinNet uint32
|
||||||
|
|
||||||
|
// Constants used to indicate the message bitcoin network. They can also be
|
||||||
|
// used to seek to the next message when a stream's state is unknown, but
|
||||||
|
// this package does not provide that functionality since it's generally a
|
||||||
|
// better idea to simply disconnect clients that are misbehaving over TCP.
|
||||||
|
const (
|
||||||
|
// MainNet represents the main bitcoin network.
|
||||||
|
MainNet BitcoinNet = 0xd9b4bef9
|
||||||
|
|
||||||
|
// TestNet represents the regression test network.
|
||||||
|
TestNet BitcoinNet = 0xdab5bffa
|
||||||
|
|
||||||
|
// TestNet3 represents the test network (version 3).
|
||||||
|
TestNet3 BitcoinNet = 0x0709110b
|
||||||
|
|
||||||
|
// SimNet represents the simulation test network.
|
||||||
|
SimNet BitcoinNet = 0x12141c16
|
||||||
|
)
|
||||||
|
|
||||||
|
// bnStrings is a map of bitcoin networks back to their constant names for
|
||||||
|
// pretty printing.
|
||||||
|
var bnStrings = map[BitcoinNet]string{
|
||||||
|
MainNet: "MainNet",
|
||||||
|
TestNet: "TestNet",
|
||||||
|
TestNet3: "TestNet3",
|
||||||
|
SimNet: "SimNet",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the BitcoinNet in human-readable form.
|
||||||
|
func (n BitcoinNet) String() string {
|
||||||
|
if s, ok := bnStrings[n]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("Unknown BitcoinNet (%d)", uint32(n))
|
||||||
|
}
|
57
wire/protocol_test.go
Normal file
57
wire/protocol_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestServiceFlagStringer tests the stringized output for service flag types.
|
||||||
|
func TestServiceFlagStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.ServiceFlag
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0x0"},
|
||||||
|
{wire.SFNodeNetwork, "SFNodeNetwork"},
|
||||||
|
{0xffffffff, "SFNodeNetwork|0xfffffffe"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBitcoinNetStringer tests the stringized output for bitcoin net types.
|
||||||
|
func TestBitcoinNetStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in wire.BitcoinNet
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{wire.MainNet, "MainNet"},
|
||||||
|
{wire.TestNet, "TestNet"},
|
||||||
|
{wire.TestNet3, "TestNet3"},
|
||||||
|
{wire.SimNet, "SimNet"},
|
||||||
|
{0xffffffff, "Unknown BitcoinNet (4294967295)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
wire/shahash.go
Normal file
108
wire/shahash.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Size of array used to store sha hashes. See ShaHash.
|
||||||
|
const HashSize = 32
|
||||||
|
|
||||||
|
// MaxHashStringSize is the maximum length of a ShaHash hash string.
|
||||||
|
const MaxHashStringSize = HashSize * 2
|
||||||
|
|
||||||
|
// ErrHashStrSize describes an error that indicates the caller specified a hash
|
||||||
|
// string that has too many characters.
|
||||||
|
var ErrHashStrSize = fmt.Errorf("max hash string length is %v bytes", MaxHashStringSize)
|
||||||
|
|
||||||
|
// ShaHash is used in several of the bitcoin messages and common structures. It
|
||||||
|
// typically represents the double sha256 of data.
|
||||||
|
type ShaHash [HashSize]byte
|
||||||
|
|
||||||
|
// String returns the ShaHash as the hexadecimal string of the byte-reversed
|
||||||
|
// hash.
|
||||||
|
func (hash ShaHash) String() string {
|
||||||
|
for i := 0; i < HashSize/2; i++ {
|
||||||
|
hash[i], hash[HashSize-1-i] = hash[HashSize-1-i], hash[i]
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes which represent the hash as a byte slice.
|
||||||
|
func (hash *ShaHash) Bytes() []byte {
|
||||||
|
newHash := make([]byte, HashSize)
|
||||||
|
copy(newHash, hash[:])
|
||||||
|
|
||||||
|
return newHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes sets the bytes which represent the hash. An error is returned if
|
||||||
|
// the number of bytes passed in is not HashSize.
|
||||||
|
func (hash *ShaHash) SetBytes(newHash []byte) error {
|
||||||
|
nhlen := len(newHash)
|
||||||
|
if nhlen != HashSize {
|
||||||
|
return fmt.Errorf("invalid sha length of %v, want %v", nhlen,
|
||||||
|
HashSize)
|
||||||
|
}
|
||||||
|
copy(hash[:], newHash[0:HashSize])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual returns true if target is the same as hash.
|
||||||
|
func (hash *ShaHash) IsEqual(target *ShaHash) bool {
|
||||||
|
return bytes.Equal(hash[:], target[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShaHash returns a new ShaHash from a byte slice. An error is returned if
|
||||||
|
// the number of bytes passed in is not HashSize.
|
||||||
|
func NewShaHash(newHash []byte) (*ShaHash, error) {
|
||||||
|
var sh ShaHash
|
||||||
|
err := sh.SetBytes(newHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sh, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShaHashFromStr creates a ShaHash from a hash string. The string should be
|
||||||
|
// the hexadecimal string of a byte-reversed hash, but any missing characters
|
||||||
|
// result in zero padding at the end of the ShaHash.
|
||||||
|
func NewShaHashFromStr(hash string) (*ShaHash, error) {
|
||||||
|
// Return error if hash string is too long.
|
||||||
|
if len(hash) > MaxHashStringSize {
|
||||||
|
return nil, ErrHashStrSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex decoder expects the hash to be a multiple of two.
|
||||||
|
if len(hash)%2 != 0 {
|
||||||
|
hash = "0" + hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string hash to bytes.
|
||||||
|
buf, err := hex.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Un-reverse the decoded bytes, copying into in leading bytes of a
|
||||||
|
// ShaHash. There is no need to explicitly pad the result as any
|
||||||
|
// missing (when len(buf) < HashSize) bytes from the decoded hex string
|
||||||
|
// will remain zeros at the end of the ShaHash.
|
||||||
|
var ret ShaHash
|
||||||
|
blen := len(buf)
|
||||||
|
mid := blen / 2
|
||||||
|
if blen%2 != 0 {
|
||||||
|
mid++
|
||||||
|
}
|
||||||
|
blen--
|
||||||
|
for i, b := range buf[:mid] {
|
||||||
|
ret[i], ret[blen-i] = buf[blen-i], b
|
||||||
|
}
|
||||||
|
return &ret, nil
|
||||||
|
}
|
182
wire/shahash_test.go
Normal file
182
wire/shahash_test.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package wire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestShaHash tests the ShaHash API.
|
||||||
|
func TestShaHash(t *testing.T) {
|
||||||
|
|
||||||
|
// Hash of block 234439.
|
||||||
|
blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef"
|
||||||
|
blockHash, err := wire.NewShaHashFromStr(blockHashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash of block 234440 as byte slice.
|
||||||
|
buf := []byte{
|
||||||
|
0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1,
|
||||||
|
0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8,
|
||||||
|
0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f,
|
||||||
|
0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := wire.NewShaHash(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHash: unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure proper size.
|
||||||
|
if len(hash) != wire.HashSize {
|
||||||
|
t.Errorf("NewShaHash: hash length mismatch - got: %v, want: %v",
|
||||||
|
len(hash), wire.HashSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure contents match.
|
||||||
|
if !bytes.Equal(hash[:], buf) {
|
||||||
|
t.Errorf("NewShaHash: hash contents mismatch - got: %v, want: %v",
|
||||||
|
hash[:], buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure contents of hash of block 234440 don't match 234439.
|
||||||
|
if hash.IsEqual(blockHash) {
|
||||||
|
t.Errorf("IsEqual: hash contents should not match - got: %v, want: %v",
|
||||||
|
hash, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set hash from byte slice and ensure contents match.
|
||||||
|
err = hash.SetBytes(blockHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SetBytes: %v", err)
|
||||||
|
}
|
||||||
|
if !hash.IsEqual(blockHash) {
|
||||||
|
t.Errorf("IsEqual: hash contents mismatch - got: %v, want: %v",
|
||||||
|
hash, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid size for SetBytes.
|
||||||
|
err = hash.SetBytes([]byte{0x00})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("SetBytes: failed to received expected err - got: nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid size for NewShaHash.
|
||||||
|
invalidHash := make([]byte, wire.HashSize+1)
|
||||||
|
_, err = wire.NewShaHash(invalidHash)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("NewShaHash: failed to received expected err - got: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestShaHashString tests the stringized output for sha hashes.
|
||||||
|
func TestShaHashString(t *testing.T) {
|
||||||
|
// Block 100000 hash.
|
||||||
|
wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hash := wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
})
|
||||||
|
|
||||||
|
hashStr := hash.String()
|
||||||
|
if hashStr != wantStr {
|
||||||
|
t.Errorf("String: wrong hash string - got %v, want %v",
|
||||||
|
hashStr, wantStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewShaHashFromStr executes tests against the NewShaHashFromStr function.
|
||||||
|
func TestNewShaHashFromStr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
want wire.ShaHash
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
// Genesis hash.
|
||||||
|
{
|
||||||
|
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||||
|
mainNetGenesisHash,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Genesis hash with stripped leading zeros.
|
||||||
|
{
|
||||||
|
"19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||||
|
mainNetGenesisHash,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Empty string.
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
wire.ShaHash{},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Single digit hash.
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Block 203707 with stripped leading zeros.
|
||||||
|
{
|
||||||
|
"3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc",
|
||||||
|
wire.ShaHash([wire.HashSize]byte{ // Make go vet happy.
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hash string that is too long.
|
||||||
|
{
|
||||||
|
"01234567890123456789012345678901234567890123456789012345678912345",
|
||||||
|
wire.ShaHash{},
|
||||||
|
wire.ErrHashStrSize,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hash string that is contains non-hex chars.
|
||||||
|
{
|
||||||
|
"abcdefg",
|
||||||
|
wire.ShaHash{},
|
||||||
|
hex.InvalidByteError('g'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unexpectedErrStr := "NewShaHashFromStr #%d failed to detect expected error - got: %v want: %v"
|
||||||
|
unexpectedResultStr := "NewShaHashFromStr #%d got: %v want: %v"
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result, err := wire.NewShaHashFromStr(test.in)
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf(unexpectedErrStr, i, err, test.err)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
// Got expected error. Move on to the next test.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !test.want.IsEqual(result) {
|
||||||
|
t.Errorf(unexpectedResultStr, i, result, &test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue