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…
Reference in a new issue