Initial implementation.
This commit is contained in:
parent
b4ebd0057b
commit
69b27dd5d3
57 changed files with 8557 additions and 1 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
122
README.md
122
README.md
|
@ -1,4 +1,124 @@
|
||||||
btcwire
|
btcwire
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Package btcwire implements the bitcoin wire protocol.
|
Package btcwire implements the bitcoin wire protocol. A comprehensive suite of
|
||||||
|
tests is provided to ensure proper functionality. See `test_coverage.txt` for
|
||||||
|
the gocov coverage report. Alternatively, if you are running a POSIX OS, you
|
||||||
|
can run the `cov_report.sh` script for a real-time report. Package btcwire is
|
||||||
|
licensed under the liberal ISC license.
|
||||||
|
|
||||||
|
There is an associated blog post about the release of this package
|
||||||
|
[here](https://blog.conformal.com/btcwire-the-bitcoin-wire-protocol-package-from-btcd/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://godoc.org/github.com/conformal/btcwire
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/conformal/btcwire
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/conformal/btcwire
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bitcoin Message Overview
|
||||||
|
|
||||||
|
The bitcoin protocol consists of exchanging messages between peers. Each message
|
||||||
|
is preceded by a header which identifies information about it such as which
|
||||||
|
bitcoin network it is a part of, its type, how big it is, and a checksum to
|
||||||
|
verify validity. All encoding and decoding of message headers is handled by this
|
||||||
|
package.
|
||||||
|
|
||||||
|
To accomplish this, there is a generic interface for bitcoin messages named
|
||||||
|
`Message` which allows messages of any type to be read, written, or passed
|
||||||
|
around through channels, functions, etc. In addition, concrete implementations
|
||||||
|
of most of the currently supported bitcoin messages are provided. For these
|
||||||
|
supported messages, all of the details of marshalling and unmarshalling to and
|
||||||
|
from the wire using bitcoin encoding are handled so the caller doesn't have to
|
||||||
|
concern themselves with the specifics.
|
||||||
|
|
||||||
|
## Reading Messages Example
|
||||||
|
|
||||||
|
In order to unmarshal bitcoin messages from the wire, use the `ReadMessage`
|
||||||
|
function. It accepts any `io.Reader`, but typically this will be a `net.Conn`
|
||||||
|
to a remote node running a bitcoin peer. Example syntax is:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Use the most recent protocol verison supported by the package and the
|
||||||
|
// main bitcoin network.
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
btcnet := btcwire.MainNet
|
||||||
|
|
||||||
|
// Reads and validates the next bitcoin message from conn using the
|
||||||
|
// protocol version pver and the bitcoin network btcnet. The returns
|
||||||
|
// are a btcwire.Message, a []byte which contains the unmarshalled
|
||||||
|
// raw payload, and a possible error.
|
||||||
|
msg, rawPayload, err := btcwire.ReadMessage(conn, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the package documentation for details on determining the message type.
|
||||||
|
|
||||||
|
## Writing Messages Example
|
||||||
|
|
||||||
|
In order to marshal bitcoin messages to the wire, use the `WriteMessage`
|
||||||
|
function. It accepts any `io.Writer`, but typically this will be a `net.Conn`
|
||||||
|
to a remote node running a bitcoin peer. Example syntax to request addresses
|
||||||
|
from a remote peer is:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Use the most recent protocol verison supported by the package and the
|
||||||
|
// main bitcoin network.
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
btcnet := btcwire.MainNet
|
||||||
|
|
||||||
|
// Create a new getaddr bitcoin message.
|
||||||
|
msg := btcwire.NewMsgGetAddr()
|
||||||
|
|
||||||
|
// Writes a bitcoin message msg to conn using the protocol version
|
||||||
|
// pver, and the bitcoin network btcnet. The return is a possible
|
||||||
|
// error.
|
||||||
|
err := btcwire.WriteMessage(conn, msg, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Implement functions for [BIP 0014](https://en.bitcoin.it/wiki/BIP_0014)
|
||||||
|
- Implement alert message decoding/encoding
|
||||||
|
- Implement bloom filter messages (filterload, filteradd, filterclear,
|
||||||
|
merkleblock) as defined in [BIP 0037](https://en.bitcoin.it/wiki/BIP_0037)
|
||||||
|
- Increase test coverage to 100%
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Package btcwire is licensed under the liberal ISC License.
|
||||||
|
|
119
blockheader.go
Normal file
119
blockheader.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockVersion is the current latest supported block version.
|
||||||
|
const BlockVersion uint32 = 2
|
||||||
|
|
||||||
|
// Version 4 bytes + Timestamp 4 bytes + Bits 4 bytes + Nonce 4 bytes +
|
||||||
|
// TxnCount (varInt) + PrevBlock and MarkleRoot hashes.
|
||||||
|
const maxBlockHeaderPayload = 16 + maxVarIntPayload + (HashSize * 2)
|
||||||
|
|
||||||
|
// BlockHeader defines information about a block and is used in the bitcoin
|
||||||
|
// block (MsgBlock) and headers (MsgHeaders) messages.
|
||||||
|
type BlockHeader struct {
|
||||||
|
// Version of the block. This is not the same as the protocol version.
|
||||||
|
Version uint32
|
||||||
|
|
||||||
|
// Hash of the previous block in the block chain.
|
||||||
|
PrevBlock ShaHash
|
||||||
|
|
||||||
|
// Merkle tree reference to hash of all transactions for the block.
|
||||||
|
MerkleRoot ShaHash
|
||||||
|
|
||||||
|
// Time the block was created. This is, unfortunately, encoded as a
|
||||||
|
// uint32 on the wire and therefore is limited to 2106.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Difficulty target for the block.
|
||||||
|
Bits uint32
|
||||||
|
|
||||||
|
// Nonce used to generate the block.
|
||||||
|
Nonce uint32
|
||||||
|
|
||||||
|
// Number of transactions in the block. For the bitcoin headers
|
||||||
|
// (MsgHeaders) message, this must be 0. This is encoded as a variable
|
||||||
|
// length integer on the wire.
|
||||||
|
TxnCount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockHashLen is a constant that represents how much of the block header is
|
||||||
|
// used when computing the block sha 0:blockHashLen
|
||||||
|
const blockHashLen = 80
|
||||||
|
|
||||||
|
// BlockSha computes the block identifier hash for the given block header.
|
||||||
|
func (h *BlockHeader) BlockSha(pver uint32) (sha ShaHash, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = writeBlockHeader(&buf, pver, h)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sha.SetBytes(DoubleSha256(buf.Bytes()[0:blockHashLen]))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockHeader returns a new BlockHeader using the provided previous block
|
||||||
|
// hash, merkle root hash, difficulty bits, and nonce used to generate the
|
||||||
|
// block with defaults for the remaining fields.
|
||||||
|
func NewBlockHeader(prevHash *ShaHash, merkleRootHash *ShaHash, bits uint32,
|
||||||
|
nonce uint32) *BlockHeader {
|
||||||
|
|
||||||
|
return &BlockHeader{
|
||||||
|
Version: BlockVersion,
|
||||||
|
PrevBlock: *prevHash,
|
||||||
|
MerkleRoot: *merkleRootHash,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Bits: bits,
|
||||||
|
Nonce: nonce,
|
||||||
|
TxnCount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBlockHeader reads a bitcoin block header from r.
|
||||||
|
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||||
|
var sec uint32
|
||||||
|
err := readElements(r, &bh.Version, &bh.PrevBlock, &bh.MerkleRoot, &sec,
|
||||||
|
&bh.Bits, &bh.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bh.Timestamp = time.Unix(int64(sec), 0)
|
||||||
|
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bh.TxnCount = count
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBlockHeader writes a bitcoin block header to w.
|
||||||
|
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||||
|
sec := uint32(bh.Timestamp.Unix())
|
||||||
|
err := writeElements(w, bh.Version, bh.PrevBlock, bh.MerkleRoot,
|
||||||
|
sec, bh.Bits, bh.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, bh.TxnCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
160
blockheader_test.go
Normal file
160
blockheader_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBlockHeader tests the BlockHeader API.
|
||||||
|
func TestBlockHeader(t *testing.T) {
|
||||||
|
nonce64, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
nonce := uint32(nonce64)
|
||||||
|
|
||||||
|
hash := btcwire.GenesisHash
|
||||||
|
merkleHash := btcwire.GenesisMerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
bh := btcwire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
// Ensure we get the same data back out.
|
||||||
|
if !bh.PrevBlock.IsEqual(&hash) {
|
||||||
|
t.Errorf("NewBlockHeader: wrong prev hash - got %v, want %v",
|
||||||
|
spew.Sprint(bh.PrevBlock), spew.Sprint(hash))
|
||||||
|
}
|
||||||
|
if !bh.MerkleRoot.IsEqual(&merkleHash) {
|
||||||
|
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||||
|
spew.Sprint(bh.MerkleRoot), spew.Sprint(merkleHash))
|
||||||
|
}
|
||||||
|
if bh.Bits != bits {
|
||||||
|
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||||
|
bh.Bits, bits)
|
||||||
|
}
|
||||||
|
if bh.Nonce != nonce {
|
||||||
|
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||||
|
bh.Nonce, nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockHeaderWire tests the BlockHeader wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestBlockHeaderWire(t *testing.T) {
|
||||||
|
nonce := uint32(123123) // 0x1e0f3
|
||||||
|
|
||||||
|
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||||
|
hash := btcwire.GenesisHash
|
||||||
|
merkleHash := btcwire.GenesisMerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
baseBlockHdr := &btcwire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: hash,
|
||||||
|
MerkleRoot: merkleHash,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Bits: bits,
|
||||||
|
Nonce: nonce,
|
||||||
|
TxnCount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseBlockHdrEncoded is the wire encoded bytes of baseBlockHdr.
|
||||||
|
baseBlockHdrEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, // Nonce
|
||||||
|
0x00, // TxnCount Varint
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.BlockHeader // Data to encode
|
||||||
|
out *btcwire.BlockHeader // Expected decoded data
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdr,
|
||||||
|
baseBlockHdrEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.TstWriteBlockHeader(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeBlockHeader #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the block header from wire format.
|
||||||
|
var bh btcwire.BlockHeader
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = btcwire.TstReadBlockHeader(rbuf, test.pver, &bh)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readBlockHeader #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&bh, test.out) {
|
||||||
|
t.Errorf("readBlockHeader #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
common.go
Normal file
181
common.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum payload size for a variable length integer.
|
||||||
|
const maxVarIntPayload = 9
|
||||||
|
|
||||||
|
// readElement reads the next sequence of bytes from r using little endian
|
||||||
|
// depending on the concrete type of element pointed to.
|
||||||
|
func readElement(r io.Reader, element interface{}) error {
|
||||||
|
return binary.Read(r, binary.LittleEndian, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readElements reads multiple items from r. It is equivalent to multiple
|
||||||
|
// calls to readElement.
|
||||||
|
func readElements(r io.Reader, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
err := readElement(r, element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeElement writes the little endian representation of element to w.
|
||||||
|
func writeElement(w io.Writer, element interface{}) error {
|
||||||
|
return binary.Write(w, binary.LittleEndian, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeElements writes multiple items to w. It is equivalent to multiple
|
||||||
|
// calls to writeElement.
|
||||||
|
func writeElements(w io.Writer, elements ...interface{}) error {
|
||||||
|
for _, element := range elements {
|
||||||
|
err := writeElement(w, element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readVarInt reads a variable length integer from r and returns it as a uint64.
|
||||||
|
func readVarInt(r io.Reader, pver uint32) (uint64, error) {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err := r.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rv uint64
|
||||||
|
discriminant := uint8(b[0])
|
||||||
|
switch discriminant {
|
||||||
|
case 0xff:
|
||||||
|
var u uint64
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &u)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = u
|
||||||
|
|
||||||
|
case 0xfe:
|
||||||
|
var u uint32
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &u)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = uint64(u)
|
||||||
|
|
||||||
|
case 0xfd:
|
||||||
|
var u uint16
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &u)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rv = uint64(u)
|
||||||
|
|
||||||
|
default:
|
||||||
|
rv = uint64(discriminant)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVarInt serializes val to w using a variable number of bytes depending
|
||||||
|
// on its value.
|
||||||
|
func writeVarInt(w io.Writer, pver uint32, val uint64) error {
|
||||||
|
if val > math.MaxUint32 {
|
||||||
|
err := writeElements(w, []byte{0xff}, uint64(val))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if val > math.MaxUint16 {
|
||||||
|
err := writeElements(w, []byte{0xfe}, uint32(val))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if val >= 0xfd {
|
||||||
|
err := writeElements(w, []byte{0xfd}, uint16(val))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return writeElement(w, uint8(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readVarString reads a variable length string from r and returns it as a Go
|
||||||
|
// string. A varString is encoded as a varInt containing the length of the
|
||||||
|
// string, and the bytes that represent the string itself.
|
||||||
|
func readVarString(r io.Reader, pver uint32) (string, error) {
|
||||||
|
slen, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf := make([]byte, slen)
|
||||||
|
err = readElement(r, buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVarString serializes str to w as a varInt containing the length of the
|
||||||
|
// string followed by the bytes that represent the string itself.
|
||||||
|
func writeVarString(w io.Writer, pver uint32, str string) error {
|
||||||
|
err := writeVarInt(w, pver, uint64(len(str)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeElement(w, []byte(str))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomUint64 returns a cryptographically random uint64 value. This
|
||||||
|
// unexported version takes a reader primarily to ensure the error paths
|
||||||
|
// can be properly by passing a fake reader in the tests.
|
||||||
|
func randomUint64(r io.Reader) (uint64, error) {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
n, err := r.Read(b)
|
||||||
|
if n != len(b) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint64(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomUint64 returns a cryptographically random uint64 value.
|
||||||
|
func RandomUint64() (uint64, error) {
|
||||||
|
return randomUint64(rand.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes.
|
||||||
|
func DoubleSha256(b []byte) []byte {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write(b)
|
||||||
|
sum := hasher.Sum(nil)
|
||||||
|
hasher.Reset()
|
||||||
|
hasher.Write(sum)
|
||||||
|
sum = hasher.Sum(nil)
|
||||||
|
return sum
|
||||||
|
}
|
304
common_test.go
Normal file
304
common_test.go
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeRandReader implements the io.Reader interface and is used to force
|
||||||
|
// errors in the RandomUint64 function.
|
||||||
|
type fakeRandReader struct {
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read returns the fake reader error and the lesser of the fake reader value
|
||||||
|
// and the length p.
|
||||||
|
func (r *fakeRandReader) Read(p []byte) (int, error) {
|
||||||
|
n := r.n
|
||||||
|
if n > len(p) {
|
||||||
|
n = len(p)
|
||||||
|
}
|
||||||
|
return n, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarIntWire tests wire encode and decode for variable length integers.
|
||||||
|
func TestVarIntWire(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in uint64 // Value to encode
|
||||||
|
out uint64 // Expected decoded value
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
// Single byte
|
||||||
|
{0, 0, []byte{0x00}, pver},
|
||||||
|
// Max single byte
|
||||||
|
{0xfc, 0xfc, []byte{0xfc}, pver},
|
||||||
|
// Min 2-byte
|
||||||
|
{0xfd, 0xfd, []byte{0xfd, 0x0fd, 0x00}, pver},
|
||||||
|
// Max 2-byte
|
||||||
|
{0xffff, 0xffff, []byte{0xfd, 0xff, 0xff}, pver},
|
||||||
|
// Min 4-byte
|
||||||
|
{0x10000, 0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}, pver},
|
||||||
|
// Max 4-byte
|
||||||
|
{0xffffffff, 0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, pver},
|
||||||
|
// Min 8-byte
|
||||||
|
{
|
||||||
|
0x100000000, 0x100000000,
|
||||||
|
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||||
|
pver,
|
||||||
|
},
|
||||||
|
// Max 8-byte
|
||||||
|
{
|
||||||
|
0xffffffffffffffff, 0xffffffffffffffff,
|
||||||
|
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
pver,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.TstWriteVarInt(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarInt #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeVarInt #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
val, err := btcwire.TstReadVarInt(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readVarInt #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val != test.out {
|
||||||
|
t.Errorf("readVarInt #%d\n got: %d want: %d", i,
|
||||||
|
val, test.out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarIntWireErrors performs negative tests against wire encode and decode
|
||||||
|
// of variable length integers to confirm error paths work correctly.
|
||||||
|
func TestVarIntWireErrors(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in uint64 // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force errors on discriminant.
|
||||||
|
{0, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force errors on 2-byte read/write.
|
||||||
|
{0xfd, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 4-byte read/write.
|
||||||
|
{0x10000, []byte{0xfe}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 8-byte read/write.
|
||||||
|
{0x100000000, []byte{0xff}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := btcwire.TstWriteVarInt(w, test.pver, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeVarInt #%d wrong error got: %v, want :%v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
_, err = btcwire.TstReadVarInt(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readVarInt #%d wrong error got: %v, want :%v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarStringWire tests wire encode and decode for variable length strings.
|
||||||
|
func TestVarStringWire(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// str256 is a string that takes a 2-byte varint to encode.
|
||||||
|
str256 := strings.Repeat("test", 64)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in string // String to encode
|
||||||
|
out string // String to decoded value
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
// Empty string
|
||||||
|
{"", "", []byte{0x00}, pver},
|
||||||
|
// Single byte varint + string
|
||||||
|
{"Test", "Test", append([]byte{0x04}, []byte("Test")...), pver},
|
||||||
|
// 2-byte varint + string
|
||||||
|
{str256, str256, append([]byte{0xfd, 0x00, 0x01}, []byte(str256)...), pver},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.TstWriteVarString(&buf, test.pver, test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeVarString #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeVarString #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
val, err := btcwire.TstReadVarString(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readVarString #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val != test.out {
|
||||||
|
t.Errorf("readVarString #%d\n got: %d want: %d", i,
|
||||||
|
val, test.out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVarStringWireErrors performs negative tests against wire encode and
|
||||||
|
// decode of variable length strings to confirm error paths work correctly.
|
||||||
|
func TestVarStringWireErrors(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// str256 is a string that takes a 2-byte varint to encode.
|
||||||
|
str256 := strings.Repeat("test", 64)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in string // Value to encode
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
max int // Max size of fixed buffer to induce errors
|
||||||
|
writeErr error // Expected write error
|
||||||
|
readErr error // Expected read error
|
||||||
|
}{
|
||||||
|
// Latest protocol version with intentional read/write errors.
|
||||||
|
// Force errors on empty string.
|
||||||
|
{"", []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||||
|
// Force error on single byte varint + string.
|
||||||
|
{"Test", []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
// Force errors on 2-byte varint + string.
|
||||||
|
{str256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
w := newFixedWriter(test.max)
|
||||||
|
err := btcwire.TstWriteVarString(w, test.pver, test.in)
|
||||||
|
if err != test.writeErr {
|
||||||
|
t.Errorf("writeVarString #%d wrong error got: %v, want :%v",
|
||||||
|
i, err, test.writeErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
r := newFixedReader(test.max, test.buf)
|
||||||
|
_, err = btcwire.TstReadVarString(r, test.pver)
|
||||||
|
if err != test.readErr {
|
||||||
|
t.Errorf("readVarString #%d wrong error got: %v, want :%v",
|
||||||
|
i, err, test.readErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRandomUint64 exercises the randomness of the random number generator on
|
||||||
|
// the system by ensuring the probability of the generated numbers. If the RNG
|
||||||
|
// is evenly distributed as a proper cryptographic RNG should be, there really
|
||||||
|
// should only be 1 number < 2^56 in 2^8 tries for a 64-bit number. However,
|
||||||
|
// use a higher number of 5 to really ensure the test doesn't fail unless the
|
||||||
|
// RNG is just horrendous.
|
||||||
|
func TestRandomUint64(t *testing.T) {
|
||||||
|
tries := 1 << 8 // 2^8
|
||||||
|
watermark := uint64(1 << 56) // 2^56
|
||||||
|
maxHits := 5
|
||||||
|
badRNG := "The random number generator on this system is clearly " +
|
||||||
|
"terrible since we got %d values less than %d in %d runs " +
|
||||||
|
"when only %d was expected"
|
||||||
|
|
||||||
|
numHits := 0
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64 iteration %d failed - err %v",
|
||||||
|
i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nonce < watermark {
|
||||||
|
numHits++
|
||||||
|
}
|
||||||
|
if numHits > maxHits {
|
||||||
|
str := fmt.Sprintf(badRNG, numHits, watermark, tries, maxHits)
|
||||||
|
t.Errorf("Random Uint64 iteration %d failed - %v %v", i,
|
||||||
|
str, numHits)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRandomUint64Errors uses a fake reader to force error paths to be executed
|
||||||
|
// and checks the results accordingly.
|
||||||
|
func TestRandomUint64Errors(t *testing.T) {
|
||||||
|
// Test short reads.
|
||||||
|
fr := &fakeRandReader{n: 2, err: nil}
|
||||||
|
nonce, err := btcwire.TstRandomUint64(fr)
|
||||||
|
if err != io.ErrShortBuffer {
|
||||||
|
t.Errorf("TestRandomUint64Fails: Error not expected value of %v [%v]",
|
||||||
|
io.ErrShortBuffer, err)
|
||||||
|
}
|
||||||
|
if nonce != 0 {
|
||||||
|
t.Errorf("TestRandomUint64Fails: nonce is not 0 [%v]", nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test err with full read.
|
||||||
|
fr = &fakeRandReader{n: 20, err: io.ErrClosedPipe}
|
||||||
|
nonce, err = btcwire.TstRandomUint64(fr)
|
||||||
|
if err != io.ErrClosedPipe {
|
||||||
|
t.Errorf("TestRandomUint64Fails: Error not expected value of %v [%v]",
|
||||||
|
io.ErrClosedPipe, err)
|
||||||
|
}
|
||||||
|
if nonce != 0 {
|
||||||
|
t.Errorf("TestRandomUint64Fails: nonce is not 0 [%v]", nonce)
|
||||||
|
}
|
||||||
|
}
|
17
cov_report.sh
Normal file
17
cov_report.sh
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/awx/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed it $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
type gocov >/dev/null 2>&1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/awx/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gocov test | gocov report
|
163
doc.go
Normal file
163
doc.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package btcwire implements the bitcoin wire protocol.
|
||||||
|
|
||||||
|
For the complete details of the bitcoin protocol, see the official wiki entry
|
||||||
|
at https://en.bitcoin.it/wiki/Protocol_specification. The following only serves
|
||||||
|
as a quick overview to provide information on how to use the package.
|
||||||
|
|
||||||
|
At a high level, this package provides support for marshalling and unmarshalling
|
||||||
|
supported bitcoin messages to and from the wire. This package does not deal
|
||||||
|
with the specifics of message handling such as what to do when a message is
|
||||||
|
received. This provides the caller with a high level of flexibility.
|
||||||
|
|
||||||
|
Bitcoin Message Overview
|
||||||
|
|
||||||
|
The bitcoin protocol consists of exchanging messages between peers. Each
|
||||||
|
message is preceded by a header which identifies information about it such as
|
||||||
|
which bitcoin network it is a part of, its type, how big it is, and a checksum
|
||||||
|
to verify validity. All encoding and decoding of message headers is handled by
|
||||||
|
this package.
|
||||||
|
|
||||||
|
To accomplish this, there is a generic interface for bitcoin messages named
|
||||||
|
Message which allows messages of any type to be read, written, or passed around
|
||||||
|
through channels, functions, etc. In addition, concrete implementations of most
|
||||||
|
of the currently supported bitcoin messages are provided. For these supported
|
||||||
|
messages, all of the details of marshalling and unmarshalling to and from the
|
||||||
|
wire using bitcoin encoding are handled so the caller doesn't have to concern
|
||||||
|
themselves with the specifics.
|
||||||
|
|
||||||
|
Message Interaction
|
||||||
|
|
||||||
|
The following provides a quick summary of how the bitcoin messages are intended
|
||||||
|
to interact with one another. As stated above, these interactions are not
|
||||||
|
directly handled by this package. For more in-depth details about the
|
||||||
|
appropriate interactions, see the official bitcoin protocol wiki entry at
|
||||||
|
https://en.bitcoin.it/wiki/Protocol_specification.
|
||||||
|
|
||||||
|
The initial handshake consists of two peers sending each other a version message
|
||||||
|
(MsgVersion) followed by responding with a verack message (MsgVerAck). Both
|
||||||
|
peers use the information in the version message (MsgVersion) to negotiate
|
||||||
|
things such as protocol version and supported services with each other. Once
|
||||||
|
the initial handshake is complete, the following chart indicates message
|
||||||
|
interactions in no particular order.
|
||||||
|
|
||||||
|
Peer A Sends Peer B Responds
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
getaddr message (MsgGetAddr) addr message (MsgAddr)
|
||||||
|
getblocks message (MsgGetBlocks) inv message (MsgInv)
|
||||||
|
inv message (MsgInv) getdata message (MsgGetData)
|
||||||
|
getdata message (MsgGetData) block message (MsgBlock) -or-
|
||||||
|
tx message (MsgTx) -or-
|
||||||
|
notfound message (MsgNotFound)
|
||||||
|
getheaders message (MsgGetHeaders) headers message (MsgHeaders)
|
||||||
|
ping message (MsgPing) pong message (MsgHeaders)* -or-
|
||||||
|
(none -- Ability to send message is enough)
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
* The pong message was not added until later protocol versions as defined
|
||||||
|
in BIP0031. The BIP0031Version constant can be used to detect a recent
|
||||||
|
enough protocol version for this purpose (version > BIP0031Version).
|
||||||
|
|
||||||
|
Common Parameters
|
||||||
|
|
||||||
|
There are several common parameters that arise when using this package to read
|
||||||
|
and write bitcoin messages. The following sections provide a quick overview of
|
||||||
|
these parameters so the next sections can build on them.
|
||||||
|
|
||||||
|
Protocol Version
|
||||||
|
|
||||||
|
The protocol version should be negotiated with the remote peer at a higher
|
||||||
|
level than this package via the version (MsgVersion) message exchange, however,
|
||||||
|
this package provides the btcwire.ProtocolVersion constant which indicates the
|
||||||
|
latest protocol version this package supports and is typically the value to use
|
||||||
|
for all outbound connections before a potentially lower protocol version is
|
||||||
|
negotiated.
|
||||||
|
|
||||||
|
Bitcoin Network
|
||||||
|
|
||||||
|
The bitcoin network is a magic number which is used to identify the start of a
|
||||||
|
and which bitcoin network the message applies to. This package provides the
|
||||||
|
following constants:
|
||||||
|
|
||||||
|
btcwire.MainNet
|
||||||
|
btcwire.TestNet
|
||||||
|
btcwire.TestNet3
|
||||||
|
|
||||||
|
Determining Message Type
|
||||||
|
|
||||||
|
As discussed in the bitcoin message overview section, this package reads
|
||||||
|
and writes bitcoin messages using a generic interface named Message. In
|
||||||
|
order to determine the actual concrete type of the message, use a type
|
||||||
|
switch or type assertion. An example of a type switch follows:
|
||||||
|
|
||||||
|
// Assumes msg is already a valid concrete message such as one created
|
||||||
|
// via NewMsgVersion or read via ReadMessage.
|
||||||
|
switch msg.(type) {
|
||||||
|
case *btcwire.MsgVersion:
|
||||||
|
// The message is a pointer to a MsgVersion struct.
|
||||||
|
fmt.Printf("Protocol version: %v", msg.ProtocolVersion)
|
||||||
|
case *btcwire.MsgBlock:
|
||||||
|
// The message is a pointer to a MsgBlock struct.
|
||||||
|
fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Reading Messages
|
||||||
|
|
||||||
|
In order to unmarshall bitcoin messages from the wire, use the ReadMessage
|
||||||
|
function. It accepts any io.Reader, but typically this will be a net.Conn to
|
||||||
|
a remote node running a bitcoin peer. Example syntax is:
|
||||||
|
|
||||||
|
// Reads and validates the next bitcoin message from conn using the
|
||||||
|
// protocol version pver and the bitcoin network btcnet. The returns
|
||||||
|
// are a btcwire.Message, a []byte which contains the unmarshalled
|
||||||
|
// raw payload, and a possible error.
|
||||||
|
msg, rawPayload, err := btcwire.ReadMessage(conn, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
|
||||||
|
Writing Messages
|
||||||
|
|
||||||
|
In order to marshall bitcoin messages to the wire, use the WriteMessage
|
||||||
|
function. It accepts any io.Writer, but typically this will be a net.Conn to
|
||||||
|
a remote node running a bitcoin peer. Example syntax to request addresses
|
||||||
|
from a remote peer is:
|
||||||
|
|
||||||
|
// Create a new getaddr bitcoin message.
|
||||||
|
msg := btcwire.NewMsgGetAddr()
|
||||||
|
|
||||||
|
// Writes a bitcoin message msg to conn using the protocol version
|
||||||
|
// pver, and the bitcoin network btcnet. The return is a possible
|
||||||
|
// error.
|
||||||
|
err := btcwire.WriteMessage(conn, msg, pver, btcnet)
|
||||||
|
if err != nil {
|
||||||
|
// Log and handle the error
|
||||||
|
}
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Most errors returned by this package are either the raw errors provided by
|
||||||
|
underlying calls to read/write from streams, or raw strings that describe
|
||||||
|
the error. See the documentation of each function for any exceptions. NOTE:
|
||||||
|
This will change soon as the package should return errors that can be
|
||||||
|
programatically tested.
|
||||||
|
|
||||||
|
Bitcoin Improvement Proposals
|
||||||
|
|
||||||
|
This package includes spec changes outlined by the following BIPs:
|
||||||
|
|
||||||
|
BIP0031 (https://en.bitcoin.it/wiki/BIP_0031)
|
||||||
|
BIP0035 (https://en.bitcoin.it/wiki/BIP_0035)
|
||||||
|
|
||||||
|
Other important information
|
||||||
|
|
||||||
|
The package does not yet implement BIP0037 (https://en.bitcoin.it/wiki/BIP_0037)
|
||||||
|
and therefore does not recognize filterload, filteradd, filterclear, or
|
||||||
|
merkleblock messages.
|
||||||
|
*/
|
||||||
|
package btcwire
|
62
fakeconn_test.go
Normal file
62
fakeconn_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeConn implements the net.Conn interface and is used to test functions
|
||||||
|
// which work with a net.Conn without having to actually make any real
|
||||||
|
// connections.
|
||||||
|
type fakeConn struct {
|
||||||
|
localAddr net.Addr
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Write(b []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the localAddr field of the fake connection and satisfies
|
||||||
|
// the net.Conn interface.
|
||||||
|
func (c *fakeConn) LocalAddr() net.Addr {
|
||||||
|
return c.localAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remoteAddr field of the fake connection and satisfies
|
||||||
|
// the net.Conn interface.
|
||||||
|
func (c *fakeConn) RemoteAddr() net.Addr {
|
||||||
|
return c.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline doesn't do anything. It just satisfies the net.Conn interface.
|
||||||
|
func (c *fakeConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline doesn't do anything. It just satisfies the net.Conn
|
||||||
|
// interface.
|
||||||
|
func (c *fakeConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline doesn't do anything. It just satisfies the net.Conn
|
||||||
|
// interface.
|
||||||
|
func (c *fakeConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
40
fakemessage_test.go
Normal file
40
fakemessage_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeMessage implements the btcwire.Message interface and is used to force
|
||||||
|
// errors.
|
||||||
|
type fakeMessage struct {
|
||||||
|
command string
|
||||||
|
maxPayload uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode doesn't do anything. It just satisfies the btcwire.Message
|
||||||
|
// interface.
|
||||||
|
func (msg *fakeMessage) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode doesn't do anything. It just satisfies the btcwire.Message
|
||||||
|
// interface.
|
||||||
|
func (msg *fakeMessage) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the command field of the fake message and satisfies the
|
||||||
|
// btcwire.Message interface.
|
||||||
|
func (msg *fakeMessage) Command() string {
|
||||||
|
return msg.command
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the maxPayload field of the fake message and satisfies the
|
||||||
|
// btcwire.Message interface.
|
||||||
|
func (msg *fakeMessage) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return msg.maxPayload
|
||||||
|
}
|
67
fixedIO_test.go
Normal file
67
fixedIO_test.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fixedWriter implements the io.Writer interface and intentially allows
|
||||||
|
// testing of error paths by forcing short writes.
|
||||||
|
type fixedWriter struct {
|
||||||
|
b []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write ...
|
||||||
|
func (w *fixedWriter) Write(p []byte) (n int, err error) {
|
||||||
|
lenp := len(p)
|
||||||
|
if w.pos+lenp > cap(w.b) {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
n = lenp
|
||||||
|
w.pos += copy(w.b[w.pos:], p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes ...
|
||||||
|
func (w *fixedWriter) Bytes() []byte {
|
||||||
|
return w.b
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFixedWriter...
|
||||||
|
func newFixedWriter(max int) *fixedWriter {
|
||||||
|
b := make([]byte, max, max)
|
||||||
|
fw := fixedWriter{b, 0}
|
||||||
|
return &fw
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixedReader implements the io.Reader interface and intentially allows
|
||||||
|
// testing of error paths by forcing short reads.
|
||||||
|
type fixedReader struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
iobuf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read ....
|
||||||
|
func (fr *fixedReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = fr.iobuf.Read(p)
|
||||||
|
fr.pos += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFixedReader ...
|
||||||
|
func newFixedReader(max int, buf []byte) *fixedReader {
|
||||||
|
b := make([]byte, max, max)
|
||||||
|
if buf != nil {
|
||||||
|
copy(b[:], buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
iobuf := bytes.NewBuffer(b)
|
||||||
|
fr := fixedReader{b, 0, iobuf}
|
||||||
|
return &fr
|
||||||
|
}
|
83
genesis.go
Normal file
83
genesis.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenesisHash is the hash of the first block in the block chain (genesis
|
||||||
|
// block).
|
||||||
|
var GenesisHash ShaHash = ShaHash{
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenesisMerkleRoot is the hash of the first transaction in the genesis block.
|
||||||
|
var GenesisMerkleRoot ShaHash = ShaHash{
|
||||||
|
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||||
|
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||||
|
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||||
|
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenesisBlock defines the genesis block of the block chain which serves as the
|
||||||
|
// public transaction ledger.
|
||||||
|
var GenesisBlock MsgBlock = MsgBlock{
|
||||||
|
Header: BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: ShaHash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
MerkleRoot: GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Bits: 0x1d00ffff, // 486604799
|
||||||
|
Nonce: 0x7c2bac1d, // 2083236893
|
||||||
|
TxnCount: 1,
|
||||||
|
},
|
||||||
|
Transactions: []*MsgTx{
|
||||||
|
&MsgTx{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*TxIn{
|
||||||
|
&TxIn{
|
||||||
|
PreviousOutpoint: OutPoint{
|
||||||
|
Hash: ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */
|
||||||
|
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */
|
||||||
|
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */
|
||||||
|
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */
|
||||||
|
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */
|
||||||
|
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */
|
||||||
|
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/
|
||||||
|
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */
|
||||||
|
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/
|
||||||
|
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*TxOut{
|
||||||
|
&TxOut{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */
|
||||||
|
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */
|
||||||
|
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */
|
||||||
|
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */
|
||||||
|
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */
|
||||||
|
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */
|
||||||
|
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */
|
||||||
|
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */
|
||||||
|
0x1d, 0x5f, 0xac, /* |._.| */
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
87
genesis_test.go
Normal file
87
genesis_test.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGenesisBlock tests the genesis block for validity by checking the encoded
|
||||||
|
// bytes and hashes.
|
||||||
|
func TestGenesisBlock(t *testing.T) {
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Encode the genesis block to raw bytes.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.GenesisBlock.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TestGenesisBlock: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the encoded block matches the expected bytes.
|
||||||
|
if !bytes.Equal(buf.Bytes(), genesisBlockBytes) {
|
||||||
|
t.Errorf("TestGenesisBlock: Genesis block does not appear valid - "+
|
||||||
|
"got %v, want %v", spew.Sdump(buf.Bytes()),
|
||||||
|
spew.Sdump(genesisBlockBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check hash of the block against expected hash.
|
||||||
|
hash, err := btcwire.GenesisBlock.Header.BlockSha(pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BlockSha: %v", err)
|
||||||
|
}
|
||||||
|
if !btcwire.GenesisHash.IsEqual(&hash) {
|
||||||
|
t.Errorf("TestGenesisBlock: Genesis block hash does not appear valid - "+
|
||||||
|
"got %v, want %v", spew.Sdump(hash),
|
||||||
|
spew.Sdump(btcwire.GenesisHash))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// genesisBlockBytes are the wire encoded bytes for the genesis block as of
|
||||||
|
// protocol version 60002.
|
||||||
|
var genesisBlockBytes = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */
|
||||||
|
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */
|
||||||
|
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */
|
||||||
|
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */
|
||||||
|
0x4b, 0x1e, 0x5e, 0x4a, 0x29, 0xab, 0x5f, 0x49, /* |K.^J)._I| */
|
||||||
|
0xff, 0xff, 0x00, 0x1d, 0x1d, 0xac, 0x2b, 0x7c, /* |......+|| */
|
||||||
|
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
||||||
|
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */
|
||||||
|
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */
|
||||||
|
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */
|
||||||
|
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */
|
||||||
|
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */
|
||||||
|
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */
|
||||||
|
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */
|
||||||
|
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */
|
||||||
|
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */
|
||||||
|
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */
|
||||||
|
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */
|
||||||
|
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */
|
||||||
|
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */
|
||||||
|
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */
|
||||||
|
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */
|
||||||
|
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */
|
||||||
|
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */
|
||||||
|
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */
|
||||||
|
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/
|
||||||
|
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
||||||
|
}
|
100
internal_test.go
Normal file
100
internal_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the btcwire package rather than than the
|
||||||
|
btcwire_test package so it can bridge access to the internals to properly test
|
||||||
|
cases which are either not possible or can't reliably be tested via the public
|
||||||
|
interface. The functions are only exported while the tests are being run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TstRandomUint64 makes the internal randomUint64 function available to the
|
||||||
|
// test package.
|
||||||
|
func TstRandomUint64(r io.Reader) (uint64, error) {
|
||||||
|
return randomUint64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadVarInt makes the internal readVarInt function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadVarInt(r io.Reader, pver uint32) (uint64, error) {
|
||||||
|
return readVarInt(r, pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteVarInt makes the internal writeVarInt function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteVarInt(w io.Writer, pver uint32, val uint64) error {
|
||||||
|
return writeVarInt(w, pver, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadVarString makes the internal readVarString function available to the
|
||||||
|
// test package.
|
||||||
|
func TstReadVarString(r io.Reader, pver uint32) (string, error) {
|
||||||
|
return readVarString(r, pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteVarString makes the internal writeVarString function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteVarString(w io.Writer, pver uint32, str string) error {
|
||||||
|
return writeVarString(w, pver, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadNetAddress makes the internal readNetAddress function available to
|
||||||
|
// the test package.
|
||||||
|
func TstReadNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
return readNetAddress(r, pver, na, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteNetAddress makes the internal writeNetAddress function available to
|
||||||
|
// the test package.
|
||||||
|
func TstWriteNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
return writeNetAddress(w, pver, na, ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstMaxNetAddressPayload makes the internal maxNetAddressPayload function
|
||||||
|
// available to the test package.
|
||||||
|
func TstMaxNetAddressPayload(pver uint32) uint32 {
|
||||||
|
return maxNetAddressPayload(pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadInvVect makes the internal readInvVect function available to the test
|
||||||
|
// package.
|
||||||
|
func TstReadInvVect(r io.Reader, pver uint32, iv *InvVect) error {
|
||||||
|
return readInvVect(r, pver, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteInvVect makes the internal writeInvVect function available to the
|
||||||
|
// test package.
|
||||||
|
func TstWriteInvVect(w io.Writer, pver uint32, iv *InvVect) error {
|
||||||
|
return writeInvVect(w, pver, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadBlockHeader makes the internal readBlockHeader function available to
|
||||||
|
// the test package.
|
||||||
|
func TstReadBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||||
|
return readBlockHeader(r, pver, bh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstWriteBlockHeader makes the internal writeBlockHeader function available to
|
||||||
|
// the test package.
|
||||||
|
func TstWriteBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||||
|
return writeBlockHeader(w, pver, bh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadMessage makes the internal readMessage function available to
|
||||||
|
// the test package.
|
||||||
|
func TstReadMessage(r io.Reader, pver uint32, hdr *messageHeader) (Message, []byte, error) {
|
||||||
|
return readMessage(r, pver, hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TstReadMessageHeader makes the internal readMessageHeader function available
|
||||||
|
// to the test package.
|
||||||
|
func TstReadMessageHeader(r io.Reader) (*messageHeader, error) {
|
||||||
|
return readMessageHeader(r)
|
||||||
|
}
|
79
invvect.go
Normal file
79
invvect.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxInvPerMsg is the maximum number of inventory vectors that can be in a
|
||||||
|
// single bitcoin inv message.
|
||||||
|
MaxInvPerMsg = 50000
|
||||||
|
|
||||||
|
// Maximum payload size for an inventory vector.
|
||||||
|
maxInvVectPayload = 4 + HashSize
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvType represents the allowed types of inventory vectors. See InvVect.
|
||||||
|
type InvType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvVect_Error InvType = 0
|
||||||
|
InvVect_Tx InvType = 1
|
||||||
|
InvVect_Block InvType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of service flags back to their constant names for pretty printing.
|
||||||
|
var ivStrings = map[InvType]string{
|
||||||
|
InvVect_Error: "ERROR",
|
||||||
|
InvVect_Tx: "MSG_TX",
|
||||||
|
InvVect_Block: "MSG_BLOCK",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the InvType in human-readable form.
|
||||||
|
func (invtype InvType) String() string {
|
||||||
|
if s, ok := ivStrings[invtype]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("Unknown InvType (%d)", uint32(invtype))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvVect defines a bitcoin inventory vector which is used to describe data,
|
||||||
|
// as specified by the Type field, that a peer wants, has, or does not have to
|
||||||
|
// another peer.
|
||||||
|
type InvVect struct {
|
||||||
|
Type InvType // Type of data
|
||||||
|
Hash ShaHash // Hash of the data
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInvVect returns a new InvVect using the provided type and hash.
|
||||||
|
func NewInvVect(typ InvType, hash *ShaHash) *InvVect {
|
||||||
|
return &InvVect{
|
||||||
|
Type: typ,
|
||||||
|
Hash: *hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInvVect reads an encoded InvVect from r depending on the protocol
|
||||||
|
// version.
|
||||||
|
func readInvVect(r io.Reader, pver uint32, iv *InvVect) error {
|
||||||
|
err := readElements(r, &iv.Type, &iv.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInvVect serializes an InvVect to w depending on the protocol version.
|
||||||
|
func writeInvVect(w io.Writer, pver uint32, iv *InvVect) error {
|
||||||
|
err := writeElements(w, iv.Type, iv.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
268
invvect_test.go
Normal file
268
invvect_test.go
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInvVectStringer tests the stringized output for inventory vector types.
|
||||||
|
func TestInvTypeStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.InvType
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{btcwire.InvVect_Error, "ERROR"},
|
||||||
|
{btcwire.InvVect_Tx, "MSG_TX"},
|
||||||
|
{btcwire.InvVect_Block, "MSG_BLOCK"},
|
||||||
|
{0xffffffff, "Unknown InvType (4294967295)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvVect tests the InvVect API.
|
||||||
|
func TestInvVect(t *testing.T) {
|
||||||
|
ivType := btcwire.InvVect_Block
|
||||||
|
hash := btcwire.ShaHash{}
|
||||||
|
|
||||||
|
// Ensure we get the same payload and signature back out.
|
||||||
|
iv := btcwire.NewInvVect(ivType, &hash)
|
||||||
|
if iv.Type != ivType {
|
||||||
|
t.Errorf("NewInvVect: wrong type - got %v, want %v",
|
||||||
|
iv.Type, ivType)
|
||||||
|
}
|
||||||
|
if !iv.Hash.IsEqual(&hash) {
|
||||||
|
t.Errorf("NewInvVect: wrong hash - got %v, want %v",
|
||||||
|
spew.Sdump(iv.Hash), spew.Sdump(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvVectWire tests the InvVect wire encode and decode for various
|
||||||
|
// protocol versions and supported inventory vector types.
|
||||||
|
func TestInvVectWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
baseHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// errInvVect is an inventory vector with an error.
|
||||||
|
errInvVect := btcwire.InvVect{
|
||||||
|
Type: btcwire.InvVect_Error,
|
||||||
|
Hash: btcwire.ShaHash{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// errInvVectEncoded is the wire encoded bytes of errInvVect.
|
||||||
|
errInvVectEncoded := []byte{
|
||||||
|
0x00, 0x00, 0x00, 0x00, // InvVect_Error
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // No hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// txInvVect is an inventory vector representing a transaction.
|
||||||
|
txInvVect := btcwire.InvVect{
|
||||||
|
Type: btcwire.InvVect_Tx,
|
||||||
|
Hash: *baseHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// txInvVectEncoded is the wire encoded bytes of txInvVect.
|
||||||
|
txInvVectEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvVect_Tx
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockInvVect is an inventory vector representing a block.
|
||||||
|
blockInvVect := btcwire.InvVect{
|
||||||
|
Type: btcwire.InvVect_Block,
|
||||||
|
Hash: *baseHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockInvVectEncoded is the wire encoded bytes of blockInvVect.
|
||||||
|
blockInvVectEncoded := []byte{
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvVect_Block
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.InvVect // NetAddress to encode
|
||||||
|
out btcwire.InvVect // Expected decoded NetAddress
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion error inventory vector.
|
||||||
|
{
|
||||||
|
errInvVect,
|
||||||
|
errInvVect,
|
||||||
|
errInvVectEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion tx inventory vector.
|
||||||
|
{
|
||||||
|
txInvVect,
|
||||||
|
txInvVect,
|
||||||
|
txInvVectEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion block inventory vector.
|
||||||
|
{
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVect,
|
||||||
|
blockInvVectEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.TstWriteInvVect(&buf, test.pver, &test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeInvVect #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeInvVect #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var iv btcwire.InvVect
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = btcwire.TstReadInvVect(rbuf, test.pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readInvVect #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iv, test.out) {
|
||||||
|
t.Errorf("readInvVect #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(iv), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
286
message.go
Normal file
286
message.go
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// commandSize is the fixed size of all commands in the common bitcoin message
|
||||||
|
// header. Shorter commands must be zero padded.
|
||||||
|
const commandSize = 12
|
||||||
|
|
||||||
|
// maxMessagePayload is the maximum byes a message can be regardless of other
|
||||||
|
// individual limits imposed by messages themselves.
|
||||||
|
const maxMessagePayload = (1024 * 1024 * 32) // 32MB
|
||||||
|
|
||||||
|
// Commands used in bitcoin message headers which describe the type of message.
|
||||||
|
const (
|
||||||
|
cmdVersion = "version"
|
||||||
|
cmdVerAck = "verack"
|
||||||
|
cmdGetAddr = "getaddr"
|
||||||
|
cmdAddr = "addr"
|
||||||
|
cmdGetBlocks = "getblocks"
|
||||||
|
cmdInv = "inv"
|
||||||
|
cmdGetData = "getdata"
|
||||||
|
cmdNotFound = "notfound"
|
||||||
|
cmdBlock = "block"
|
||||||
|
cmdTx = "tx"
|
||||||
|
cmdGetHeaders = "getheaders"
|
||||||
|
cmdHeaders = "headers"
|
||||||
|
cmdPing = "ping"
|
||||||
|
cmdPong = "pong"
|
||||||
|
cmdAlert = "alert"
|
||||||
|
cmdMemPool = "mempool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is an interface that describes a bitcoin message. A type that
|
||||||
|
// implements Message has complete control over the representation of its data
|
||||||
|
// and may therefore contain additional or fewer fields than those which
|
||||||
|
// are used directly in the protocol encoded message.
|
||||||
|
type Message interface {
|
||||||
|
BtcDecode(io.Reader, uint32) error
|
||||||
|
BtcEncode(io.Writer, uint32) error
|
||||||
|
Command() string
|
||||||
|
MaxPayloadLength(uint32) uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeEmptyMessage creates a message of the appropriate concrete type based
|
||||||
|
// on the command.
|
||||||
|
func makeEmptyMessage(command string) (Message, error) {
|
||||||
|
var msg Message
|
||||||
|
switch command {
|
||||||
|
case cmdVersion:
|
||||||
|
msg = &MsgVersion{}
|
||||||
|
|
||||||
|
case cmdVerAck:
|
||||||
|
msg = &MsgVerAck{}
|
||||||
|
|
||||||
|
case cmdGetAddr:
|
||||||
|
msg = &MsgGetAddr{}
|
||||||
|
|
||||||
|
case cmdAddr:
|
||||||
|
msg = &MsgAddr{}
|
||||||
|
|
||||||
|
case cmdGetBlocks:
|
||||||
|
msg = &MsgGetBlocks{}
|
||||||
|
|
||||||
|
case cmdBlock:
|
||||||
|
msg = &MsgBlock{}
|
||||||
|
|
||||||
|
case cmdInv:
|
||||||
|
msg = &MsgInv{}
|
||||||
|
|
||||||
|
case cmdGetData:
|
||||||
|
msg = &MsgGetData{}
|
||||||
|
|
||||||
|
case cmdNotFound:
|
||||||
|
msg = &MsgNotFound{}
|
||||||
|
|
||||||
|
case cmdTx:
|
||||||
|
msg = &MsgTx{}
|
||||||
|
|
||||||
|
case cmdPing:
|
||||||
|
msg = &MsgPing{}
|
||||||
|
|
||||||
|
case cmdPong:
|
||||||
|
msg = &MsgPong{}
|
||||||
|
|
||||||
|
case cmdGetHeaders:
|
||||||
|
msg = &MsgGetHeaders{}
|
||||||
|
|
||||||
|
case cmdHeaders:
|
||||||
|
msg = &MsgHeaders{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unhandled command [%s]", command)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageHeader defines the header structure for all bitcoin protocol messages.
|
||||||
|
type messageHeader struct {
|
||||||
|
magic BitcoinNet // 4 bytes
|
||||||
|
command string // 12 bytes
|
||||||
|
length uint32 // 4 bytes
|
||||||
|
checksum [4]byte // 4 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMessageHeader reads a bitcoin messager header from r.
|
||||||
|
func readMessageHeader(r io.Reader) (*messageHeader, error) {
|
||||||
|
var command [commandSize]byte
|
||||||
|
|
||||||
|
hdr := messageHeader{}
|
||||||
|
err := readElements(r, &hdr.magic, &command, &hdr.length, &hdr.checksum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip trailing zeros from command string.
|
||||||
|
hdr.command = string(bytes.TrimRight(command[:], string(0)))
|
||||||
|
|
||||||
|
// Enforce maximum message payload.
|
||||||
|
if hdr.length > maxMessagePayload {
|
||||||
|
str := "readMessageHeader: message payload is too large - " +
|
||||||
|
"Header indicates %d bytes, but max message payload is %d bytes."
|
||||||
|
return nil, fmt.Errorf(str, hdr.length, maxMessagePayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardInput reads n bytes from reader r in chunks and discards the read
|
||||||
|
// bytes. This is used to skip payloads when various errors occur and helps
|
||||||
|
// prevent rogue nodes from causing massive memory allocation through forging
|
||||||
|
// header length.
|
||||||
|
func discardInput(r io.Reader, n uint32) {
|
||||||
|
maxSize := uint32(10240) // 2k at a time
|
||||||
|
numReads := n / maxSize
|
||||||
|
bytesRemaining := n % maxSize
|
||||||
|
if n > 0 {
|
||||||
|
buf := make([]byte, maxSize)
|
||||||
|
for i := uint32(0); i < numReads; i++ {
|
||||||
|
io.ReadFull(r, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bytesRemaining > 0 {
|
||||||
|
buf := make([]byte, bytesRemaining)
|
||||||
|
io.ReadFull(r, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMessage reads the next bitcoin message from r for the provided protocol
|
||||||
|
// version and message header.
|
||||||
|
func readMessage(r io.Reader, pver uint32, hdr *messageHeader) (Message, []byte, error) {
|
||||||
|
if hdr == nil {
|
||||||
|
return nil, nil, fmt.Errorf("readMessage: nil header")
|
||||||
|
}
|
||||||
|
|
||||||
|
command := hdr.command
|
||||||
|
if !utf8.ValidString(command) {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
str := "readMessage: invalid command %v"
|
||||||
|
return nil, nil, fmt.Errorf(str, []byte(command))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create struct of appropriate message type based on the command.
|
||||||
|
msg, err := makeEmptyMessage(command)
|
||||||
|
if err != nil {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
return nil, nil, fmt.Errorf("readMessage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for maximum length based on the message type as a malicious client
|
||||||
|
// could otherwise create a well-formed header and set the length to max
|
||||||
|
// numbers in order to exhaust the machine's memory.
|
||||||
|
mpl := msg.MaxPayloadLength(pver)
|
||||||
|
if hdr.length > mpl {
|
||||||
|
discardInput(r, hdr.length)
|
||||||
|
str := "ReadMessage: payload exceeds max length - Header " +
|
||||||
|
"indicates %v bytes, but max payload size for messages of type " +
|
||||||
|
"[%v] is %v."
|
||||||
|
return nil, nil, fmt.Errorf(str, hdr.length, command, mpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload.
|
||||||
|
payload := make([]byte, hdr.length)
|
||||||
|
n, err := io.ReadFull(r, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if uint32(n) != hdr.length {
|
||||||
|
str := "readMessage: failed to read payload - Read %v " +
|
||||||
|
"bytes, but payload size is %v bytes."
|
||||||
|
return nil, nil, fmt.Errorf(str, n, hdr.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test checksum.
|
||||||
|
checksum := DoubleSha256(payload)[0:4]
|
||||||
|
if !bytes.Equal(checksum[:], hdr.checksum[:]) {
|
||||||
|
str := "readMessage: payload checksum failed - Header " +
|
||||||
|
"indicates %v, but actual checksum is %v."
|
||||||
|
return nil, nil, fmt.Errorf(str, hdr.checksum, checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal message.
|
||||||
|
pr := bytes.NewBuffer(payload)
|
||||||
|
err = msg.BtcDecode(pr, pver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMessage writes a bitcoin Message to w including the necessary header
|
||||||
|
// information.
|
||||||
|
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error {
|
||||||
|
var command [commandSize]byte
|
||||||
|
|
||||||
|
cmd := msg.Command()
|
||||||
|
if len(cmd) > commandSize {
|
||||||
|
str := "WriteMessage: command is too long [%s]"
|
||||||
|
return fmt.Errorf(str, command)
|
||||||
|
}
|
||||||
|
copy(command[:], []byte(cmd))
|
||||||
|
|
||||||
|
var bw bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&bw, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload := bw.Bytes()
|
||||||
|
lenp := len(payload)
|
||||||
|
|
||||||
|
// Enforce maximum message payload.
|
||||||
|
if lenp > maxMessagePayload {
|
||||||
|
str := "WriteMessage: message payload is too large - " +
|
||||||
|
"Encoded %d bytes, but maximum message payload is %d bytes."
|
||||||
|
return fmt.Errorf(str, lenp, maxMessagePayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create header for the message.
|
||||||
|
hdr := messageHeader{}
|
||||||
|
hdr.magic = btcnet
|
||||||
|
hdr.command = cmd
|
||||||
|
hdr.length = uint32(lenp)
|
||||||
|
copy(hdr.checksum[:], DoubleSha256(payload)[0:4])
|
||||||
|
|
||||||
|
// Write header.
|
||||||
|
err = writeElements(w, hdr.magic, command, hdr.length, hdr.checksum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write payload.
|
||||||
|
n, err := w.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != lenp {
|
||||||
|
str := "WriteMessage: failed to write payload. " +
|
||||||
|
"Wrote %v bytes, but payload size is %v bytes."
|
||||||
|
return fmt.Errorf(str, n, lenp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMessage reads, validates, and parses the next bitcoin Message from r.
|
||||||
|
func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) {
|
||||||
|
hdr, err := readMessageHeader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.magic != btcnet {
|
||||||
|
str := "ReadMessage: message from other network [%v]"
|
||||||
|
return nil, nil, fmt.Errorf(str, hdr.magic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return readMessage(r, pver, hdr)
|
||||||
|
}
|
98
message_test.go
Normal file
98
message_test.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMessage tests the Read/WriteMessage API.
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Create the various types of messages to test.
|
||||||
|
|
||||||
|
// MsgVersion.
|
||||||
|
addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}
|
||||||
|
you, err := btcwire.NewNetAddress(addrYou, btcwire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
you.Timestamp = time.Time{} // Version message has zero value timestamp.
|
||||||
|
addrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
me, err := btcwire.NewNetAddress(addrMe, btcwire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
me.Timestamp = time.Time{} // Version message has zero value timestamp.
|
||||||
|
msgVersion := btcwire.NewMsgVersion(me, you, 123123, "/test:0.0.1/", 0)
|
||||||
|
|
||||||
|
msgVerack := btcwire.NewMsgVerAck()
|
||||||
|
msgGetAddr := btcwire.NewMsgGetAddr()
|
||||||
|
msgAddr := btcwire.NewMsgAddr()
|
||||||
|
msgGetBlocks := btcwire.NewMsgGetBlocks(&btcwire.ShaHash{})
|
||||||
|
msgBlock := &blockOne
|
||||||
|
msgInv := btcwire.NewMsgInv()
|
||||||
|
msgGetData := btcwire.NewMsgGetData()
|
||||||
|
msgNotFound := btcwire.NewMsgNotFound()
|
||||||
|
msgTx := btcwire.NewMsgTx()
|
||||||
|
msgPing := btcwire.NewMsgPing(123123)
|
||||||
|
msgPong := btcwire.NewMsgPong(123123)
|
||||||
|
msgGetHeaders := btcwire.NewMsgGetHeaders()
|
||||||
|
msgHeaders := btcwire.NewMsgHeaders()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.Message // Value to encode
|
||||||
|
out btcwire.Message // Expected decoded value
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
btcnet btcwire.BitcoinNet // Network to use for wire encoding
|
||||||
|
}{
|
||||||
|
{msgVersion, msgVersion, pver, btcwire.MainNet},
|
||||||
|
{msgVerack, msgVerack, pver, btcwire.MainNet},
|
||||||
|
{msgGetAddr, msgGetAddr, pver, btcwire.MainNet},
|
||||||
|
{msgAddr, msgAddr, pver, btcwire.MainNet},
|
||||||
|
{msgGetBlocks, msgGetBlocks, pver, btcwire.MainNet},
|
||||||
|
{msgBlock, msgBlock, pver, btcwire.MainNet},
|
||||||
|
{msgInv, msgInv, pver, btcwire.MainNet},
|
||||||
|
{msgGetData, msgGetData, pver, btcwire.MainNet},
|
||||||
|
{msgNotFound, msgNotFound, pver, btcwire.MainNet},
|
||||||
|
{msgTx, msgTx, pver, btcwire.MainNet},
|
||||||
|
{msgPing, msgPing, pver, btcwire.MainNet},
|
||||||
|
{msgPong, msgPong, pver, btcwire.MainNet},
|
||||||
|
{msgGetHeaders, msgGetHeaders, pver, btcwire.MainNet},
|
||||||
|
{msgHeaders, msgHeaders, pver, btcwire.MainNet},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.WriteMessage(&buf, test.in, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("WriteMessage #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode from wire format.
|
||||||
|
rbuf := bytes.NewBuffer(buf.Bytes())
|
||||||
|
msg, _, err := btcwire.ReadMessage(rbuf, test.pver, test.btcnet)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ReadMessage #%d error %v, msg %v", i, err,
|
||||||
|
spew.Sdump(msg))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("ReadMessage #%d\n got: %v want: %v", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
msgaddr.go
Normal file
136
msgaddr.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxAddrPerMsg is the maximum number of addresses that can be in a single
|
||||||
|
// bitcoin addr message (MsgAddr).
|
||||||
|
const MaxAddrPerMsg = 1000
|
||||||
|
|
||||||
|
// MsgAddr implements the Message interface and represents a bitcoin
|
||||||
|
// addr message. It is used to provide a list of known active peers on the
|
||||||
|
// network. An active peer is considered one that has transmitted a message
|
||||||
|
// within the last 3 hours. Nodes which have not transmitted in that time
|
||||||
|
// frame should be forgotten. Each message is limited to a maximum number of
|
||||||
|
// addresses, which is currently 1000. As a result, multiple messages must
|
||||||
|
// be used to relay the full list.
|
||||||
|
//
|
||||||
|
// Use the AddAddress function to build up the list of known addresses when
|
||||||
|
// sending an addr message to another peer.
|
||||||
|
type MsgAddr struct {
|
||||||
|
AddrList []*NetAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddress adds a known active peer to the message.
|
||||||
|
func (msg *MsgAddr) AddAddress(na *NetAddress) error {
|
||||||
|
if len(msg.AddrList)+1 > MaxAddrPerMsg {
|
||||||
|
str := "MsgAddr.AddAddress: too many addresses for message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxAddrPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddrList = append(msg.AddrList, na)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddresses adds multiple known active peers to the message.
|
||||||
|
func (msg *MsgAddr) AddAddresses(netAddrs ...*NetAddress) error {
|
||||||
|
for _, na := range netAddrs {
|
||||||
|
err := msg.AddAddress(na)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAddresses removes all addresses from the message.
|
||||||
|
func (msg *MsgAddr) ClearAddresses() {
|
||||||
|
msg.AddrList = []*NetAddress{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max addresses per message.
|
||||||
|
if count > MaxAddrPerMsg {
|
||||||
|
str := "MsgAddr.BtcDecode: too many addresses in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
na := NetAddress{}
|
||||||
|
err := readNetAddress(r, pver, &na, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddAddress(&na)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Protocol versions before MultipleAddressVersion only allowed 1 address
|
||||||
|
// per message.
|
||||||
|
count := len(msg.AddrList)
|
||||||
|
if pver < MultipleAddressVersion && count > 1 {
|
||||||
|
str := "MsgAddr.BtcDecode: too many addresses in message " +
|
||||||
|
"for protocol version [version %v max 1]"
|
||||||
|
return fmt.Errorf(str, pver)
|
||||||
|
|
||||||
|
}
|
||||||
|
if count > MaxAddrPerMsg {
|
||||||
|
str := "MsgAddr.BtcDecode: too many addresses in message [max %v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, na := range msg.AddrList {
|
||||||
|
err = writeNetAddress(w, pver, na, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) Command() string {
|
||||||
|
return cmdAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAddr) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
if pver < MultipleAddressVersion {
|
||||||
|
// Num addresses (varInt) + a single net addresses.
|
||||||
|
return maxVarIntPayload + maxNetAddressPayload(pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
return maxVarIntPayload + (MaxAddrPerMsg * maxNetAddressPayload(pver))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgAddr returns a new bitcoin addr message that conforms to the
|
||||||
|
// Message interface. See MsgAddr for details.
|
||||||
|
func NewMsgAddr() *MsgAddr {
|
||||||
|
return &MsgAddr{}
|
||||||
|
}
|
205
msgaddr_test.go
Normal file
205
msgaddr_test.go
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAddr tests the MsgAddr API.
|
||||||
|
func TestAddr(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "addr"
|
||||||
|
msg := btcwire.NewMsgAddr()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(30009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure NetAddresses are added properly.
|
||||||
|
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
na, err := btcwire.NewNetAddress(tcpAddr, btcwire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
err = msg.AddAddress(na)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddAddress: %v", err)
|
||||||
|
}
|
||||||
|
if msg.AddrList[0] != na {
|
||||||
|
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the address list is cleared properly.
|
||||||
|
msg.ClearAddresses()
|
||||||
|
if len(msg.AddrList) != 0 {
|
||||||
|
t.Errorf("ClearAddresses: address list is not empty - "+
|
||||||
|
"got %v [%v], want %v", len(msg.AddrList),
|
||||||
|
spew.Sprint(msg.AddrList[0]), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed addresses per message returns
|
||||||
|
// error.
|
||||||
|
for i := 0; i < btcwire.MaxAddrPerMsg+1; i++ {
|
||||||
|
err = msg.AddAddress(na)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddAddress: expected error on too many addresses " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
err = msg.AddAddresses(na)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddAddresses: expected error on too many addresses " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for protocol versions before
|
||||||
|
// timestamp was added to NetAddress.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
pver = btcwire.NetAddressTimeVersion - 1
|
||||||
|
wantPayload = uint32(26009)
|
||||||
|
maxPayload = msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for protocol versions before
|
||||||
|
// multiple addresses were allowed.
|
||||||
|
// Num addresses (varInt) + a single net addresses.
|
||||||
|
pver = btcwire.MultipleAddressVersion - 1
|
||||||
|
wantPayload = uint32(35)
|
||||||
|
maxPayload = msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddrWire tests the MsgAddr wire encode and decode for various numbers
|
||||||
|
// of addreses and protocol versions.
|
||||||
|
func TestAddrWire(t *testing.T) {
|
||||||
|
// A couple of NetAddresses to use for testing.
|
||||||
|
na := &btcwire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
na2 := &btcwire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8334,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty address message.
|
||||||
|
noAddr := btcwire.NewMsgAddr()
|
||||||
|
noAddrEncoded := []byte{
|
||||||
|
0x00, // Varint for number of addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address message with multiple addresses.
|
||||||
|
multiAddr := btcwire.NewMsgAddr()
|
||||||
|
multiAddr.AddAddresses(na, na2)
|
||||||
|
multiAddrEncoded := []byte{
|
||||||
|
0x02, // Varint for number of addresses
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8e, // Port 8334 in big-endian
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgAddr // Message to encode
|
||||||
|
out *btcwire.MsgAddr // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no addresses.
|
||||||
|
{
|
||||||
|
noAddr,
|
||||||
|
noAddr,
|
||||||
|
noAddrEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple addresses.
|
||||||
|
{
|
||||||
|
multiAddr,
|
||||||
|
multiAddr,
|
||||||
|
multiAddrEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion-1 with no addresses.
|
||||||
|
{
|
||||||
|
noAddr,
|
||||||
|
noAddr,
|
||||||
|
noAddrEncoded,
|
||||||
|
btcwire.MultipleAddressVersion - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgAddr
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
msgalert.go
Normal file
80
msgalert.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgAlert implements the Message interface and defines a bitcoin alert
|
||||||
|
// message.
|
||||||
|
//
|
||||||
|
// This is a signed message that provides notifications that the client should
|
||||||
|
// display if the signature matches the key. bitcoind/bitcoin-qt only checks
|
||||||
|
// against a signature from the core developers.
|
||||||
|
type MsgAlert struct {
|
||||||
|
// PayloadBlob is the alert payload serialized as a string so that the
|
||||||
|
// version can change but the Alert can still be passed on by older
|
||||||
|
// clients.
|
||||||
|
PayloadBlob string
|
||||||
|
|
||||||
|
// Signature is the ECDSA signature of the message.
|
||||||
|
Signature string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
var err error
|
||||||
|
msg.PayloadBlob, err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Signature, err = readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
var err error
|
||||||
|
err = writeVarString(w, pver, msg.PayloadBlob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeVarString(w, pver, msg.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) Command() string {
|
||||||
|
return cmdAlert
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgAlert) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Since this can vary depending on the message, make it the max
|
||||||
|
// size allowed.
|
||||||
|
return maxMessagePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgAlert returns a new bitcoin alert message that conforms to the Message
|
||||||
|
// interface. See MsgAlert for details.
|
||||||
|
func NewMsgAlert(payloadblob string, signature string) *MsgAlert {
|
||||||
|
return &MsgAlert{
|
||||||
|
PayloadBlob: payloadblob,
|
||||||
|
Signature: signature,
|
||||||
|
}
|
||||||
|
}
|
139
msgalert_test.go
Normal file
139
msgalert_test.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAlert tests the MsgAlert API.
|
||||||
|
func TestAlert(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
payloadblob := "some message"
|
||||||
|
signature := "some sig"
|
||||||
|
|
||||||
|
// Ensure we get the same payload and signature back out.
|
||||||
|
msg := btcwire.NewMsgAlert(payloadblob, signature)
|
||||||
|
if msg.PayloadBlob != payloadblob {
|
||||||
|
t.Errorf("NewMsgAlert: wrong payloadblob - got %v, want %v",
|
||||||
|
msg.PayloadBlob, payloadblob)
|
||||||
|
}
|
||||||
|
if msg.Signature != signature {
|
||||||
|
t.Errorf("NewMsgAlert: wrong signature - got %v, want %v",
|
||||||
|
msg.Signature, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "alert"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAlert: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(1024 * 1024 * 32)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAlertWire tests the MsgAlert wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestAlertWire(t *testing.T) {
|
||||||
|
baseAlert := btcwire.NewMsgAlert("some payload", "somesig")
|
||||||
|
baseAlertEncoded := []byte{
|
||||||
|
0x0c, // Varint for payload length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x79,
|
||||||
|
0x6c, 0x6f, 0x61, 0x64, // "some payload"
|
||||||
|
0x07, // Varint for signature length
|
||||||
|
0x73, 0x6f, 0x6d, 0x65, 0x73, 0x69, 0x67, // "somesig"
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgAlert // Message to encode
|
||||||
|
out *btcwire.MsgAlert // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseAlert,
|
||||||
|
baseAlert,
|
||||||
|
baseAlertEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseAlert,
|
||||||
|
baseAlert,
|
||||||
|
baseAlertEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseAlert,
|
||||||
|
baseAlert,
|
||||||
|
baseAlertEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseAlert,
|
||||||
|
baseAlert,
|
||||||
|
baseAlertEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseAlert,
|
||||||
|
baseAlert,
|
||||||
|
baseAlertEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgAlert
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
msgblock.go
Normal file
163
msgblock.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxBlocksPerMsg is the maximum number of blocks allowed per message.
|
||||||
|
const MaxBlocksPerMsg = 500
|
||||||
|
|
||||||
|
// TxLoc holds locator data for the offset and length of where a transaction is
|
||||||
|
// located within a MsgBlock data buffer.
|
||||||
|
type TxLoc struct {
|
||||||
|
TxStart int
|
||||||
|
TxLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgBlock implements the Message interface and represents a bitcoin
|
||||||
|
// block message. It is used to deliver block and transaction information in
|
||||||
|
// response to a getdata message (MsgGetData) for a given block hash.
|
||||||
|
//
|
||||||
|
// NOTE: Unlike the other message types which contain slices, the number of
|
||||||
|
// transactions has a specific entry (Header.TxnCount) that must be kept in
|
||||||
|
// sync. The AddTransaction and ClearTransactions functions properly sync the
|
||||||
|
// value, but if you are manually modifying the public members, you will need
|
||||||
|
// to ensure you update the Header.TxnCount when you add and remove
|
||||||
|
// transactions.
|
||||||
|
type MsgBlock struct {
|
||||||
|
Header BlockHeader
|
||||||
|
Transactions []*MsgTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTransaction adds a transaction to the message and updates Header.TxnCount
|
||||||
|
// accordingly.
|
||||||
|
func (msg *MsgBlock) AddTransaction(tx *MsgTx) error {
|
||||||
|
// TODO: Return error if adding the transaction would make the message
|
||||||
|
// too large.
|
||||||
|
msg.Transactions = append(msg.Transactions, tx)
|
||||||
|
msg.Header.TxnCount = uint64(len(msg.Transactions))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTransactions removes all transactions from the message and updates
|
||||||
|
// Header.TxnCount accordingly.
|
||||||
|
func (msg *MsgBlock) ClearTransactions() {
|
||||||
|
msg.Transactions = []*MsgTx{}
|
||||||
|
msg.Header.TxnCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readBlockHeader(r, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < msg.Header.TxnCount; i++ {
|
||||||
|
tx := MsgTx{}
|
||||||
|
err := tx.BtcDecode(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Transactions = append(msg.Transactions, &tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecodeTxLoc decodes r using the bitcoin protocol encoding into the
|
||||||
|
// receiver and returns a slice containing the start and length each transaction
|
||||||
|
// within the raw data.
|
||||||
|
func (msg *MsgBlock) BtcDecodeTxLoc(r *bytes.Buffer, pver uint32) ([]TxLoc, error) {
|
||||||
|
var fullLen int
|
||||||
|
fullLen = r.Len()
|
||||||
|
|
||||||
|
err := readBlockHeader(r, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var txLocs []TxLoc
|
||||||
|
txLocs = make([]TxLoc, msg.Header.TxnCount)
|
||||||
|
|
||||||
|
for i := uint64(0); i < msg.Header.TxnCount; i++ {
|
||||||
|
txLocs[i].TxStart = fullLen - r.Len()
|
||||||
|
tx := MsgTx{}
|
||||||
|
err := tx.BtcDecode(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Transactions = append(msg.Transactions, &tx)
|
||||||
|
txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart
|
||||||
|
}
|
||||||
|
|
||||||
|
return txLocs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// XXX: Max transactions?
|
||||||
|
msg.Header.TxnCount = uint64(len(msg.Transactions))
|
||||||
|
|
||||||
|
err := writeBlockHeader(w, pver, &msg.Header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range msg.Transactions {
|
||||||
|
err = tx.BtcEncode(w, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) Command() string {
|
||||||
|
return cmdBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Block header at 81 bytes + max transactions which can vary up to the
|
||||||
|
// max message payload.
|
||||||
|
return maxMessagePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSha computes the block identifier hash for this block.
|
||||||
|
func (msg *MsgBlock) BlockSha(pver uint32) (ShaHash, error) {
|
||||||
|
return msg.Header.BlockSha(pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxShas returns a slice of hashes of all of transactions in this block.
|
||||||
|
func (msg *MsgBlock) TxShas(pver uint32) ([]ShaHash, error) {
|
||||||
|
var shaList []ShaHash
|
||||||
|
for _, tx := range msg.Transactions {
|
||||||
|
sha, err := tx.TxSha(pver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
shaList = append(shaList, sha)
|
||||||
|
}
|
||||||
|
return shaList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgBlock returns a new bitcoin block message that conforms to the
|
||||||
|
// Message interface. See MsgBlock for details.
|
||||||
|
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {
|
||||||
|
return &MsgBlock{
|
||||||
|
Header: *blockHeader,
|
||||||
|
}
|
||||||
|
}
|
292
msgblock_test.go
Normal file
292
msgblock_test.go
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBlock tests the MsgBlock API.
|
||||||
|
func TestBlock(t *testing.T) {
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Block 1 header.
|
||||||
|
prevHash := &blockOne.Header.PrevBlock
|
||||||
|
merkleHash := &blockOne.Header.MerkleRoot
|
||||||
|
bits := blockOne.Header.Bits
|
||||||
|
nonce := blockOne.Header.Nonce
|
||||||
|
bh := btcwire.NewBlockHeader(prevHash, merkleHash, bits, nonce)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "block"
|
||||||
|
msg := btcwire.NewMsgBlock(bh)
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgBlock: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(1024 * 1024 * 32)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same block header data back out.
|
||||||
|
if !reflect.DeepEqual(&msg.Header, bh) {
|
||||||
|
t.Errorf("NewMsgBlock: wrong block header - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.Header), spew.Sdump(bh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transactions are added properly.
|
||||||
|
tx := blockOne.Transactions[0].Copy()
|
||||||
|
msg.AddTransaction(tx)
|
||||||
|
if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) {
|
||||||
|
t.Errorf("AddTransaction: wrong transactions - got %v, want %v",
|
||||||
|
spew.Sdump(msg.Transactions),
|
||||||
|
spew.Sdump(blockOne.Transactions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transactions are properly cleared.
|
||||||
|
msg.ClearTransactions()
|
||||||
|
if len(msg.Transactions) != 0 {
|
||||||
|
t.Errorf("ClearTransactions: wrong transactions - got %v, want %v",
|
||||||
|
len(msg.Transactions), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockTxShas(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 1, transaction 1 hash.
|
||||||
|
hashStr := "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
|
||||||
|
wantHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wantShas := []btcwire.ShaHash{*wantHash}
|
||||||
|
shas, err := blockOne.TxShas(pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TxShas: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(shas, wantShas) {
|
||||||
|
t.Errorf("TxShas: wrong transaction hashes - got %v, want %v",
|
||||||
|
spew.Sdump(shas), spew.Sdump(wantShas))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockSha(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 1 hash.
|
||||||
|
hashStr := "839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
|
||||||
|
wantHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the hash produced is expected.
|
||||||
|
blockHash, err := blockOne.BlockSha(pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BlockSha: %v", err)
|
||||||
|
}
|
||||||
|
if !blockHash.IsEqual(wantHash) {
|
||||||
|
t.Errorf("BlockSha: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(blockHash), spew.Sprint(wantHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockWire tests the MsgBlock wire encode and decode for various numbers
|
||||||
|
// of transaction inputs and outputs and protocol versions.
|
||||||
|
func TestBlockWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgBlock // Message to encode
|
||||||
|
out *btcwire.MsgBlock // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
&blockOne,
|
||||||
|
&blockOne,
|
||||||
|
blockOneBytes,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgBlock
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockOne btcwire.MsgBlock = btcwire.MsgBlock{
|
||||||
|
Header: btcwire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
PrevBlock: btcwire.ShaHash{
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
MerkleRoot: btcwire.ShaHash{
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e,
|
||||||
|
},
|
||||||
|
|
||||||
|
Timestamp: time.Unix(0x4966bc61, 0), // 2009-01-08 20:54:25 -0600 CST
|
||||||
|
Bits: 0x1d00ffff, // 486604799
|
||||||
|
Nonce: 0x9962e301, // 2573394689
|
||||||
|
TxnCount: 1,
|
||||||
|
},
|
||||||
|
Transactions: []*btcwire.MsgTx{
|
||||||
|
&btcwire.MsgTx{
|
||||||
|
Version: 1,
|
||||||
|
TxIn: []*btcwire.TxIn{
|
||||||
|
&btcwire.TxIn{
|
||||||
|
PreviousOutpoint: btcwire.OutPoint{
|
||||||
|
Hash: btcwire.ShaHash{},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04,
|
||||||
|
},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*btcwire.TxOut{
|
||||||
|
&btcwire.TxOut{
|
||||||
|
Value: 0x12a05f200,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LockTime: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockOneBytes = []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x01, // TxnCount
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of output transactions
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||||
|
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||||
|
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||||
|
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||||
|
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||||
|
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||||
|
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||||
|
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||||
|
0xee, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
47
msggetaddr.go
Normal file
47
msggetaddr.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetAddr implements the Message interface and represents a bitcoin
|
||||||
|
// getaddr message. It is used to request a list of known active peers on the
|
||||||
|
// network from a peer to help identify potential nodes. The list is returned
|
||||||
|
// via one or more addr messages (MsgAddr).
|
||||||
|
//
|
||||||
|
// This message has no payload.
|
||||||
|
type MsgGetAddr struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) Command() string {
|
||||||
|
return cmdGetAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetAddr) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetAddr returns a new bitcoin getaddr message that conforms to the
|
||||||
|
// Message interface. See MsgGetAddr for details.
|
||||||
|
func NewMsgGetAddr() *MsgGetAddr {
|
||||||
|
return &MsgGetAddr{}
|
||||||
|
}
|
122
msggetaddr_test.go
Normal file
122
msggetaddr_test.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetAddr tests the MsgGetAddr API.
|
||||||
|
func TestGetAddr(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getaddr"
|
||||||
|
msg := btcwire.NewMsgGetAddr()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetAddrWire tests the MsgGetAddr wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestGetAddrWire(t *testing.T) {
|
||||||
|
msgGetAddr := btcwire.NewMsgGetAddr()
|
||||||
|
msgGetAddrEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgGetAddr // Message to encode
|
||||||
|
out *btcwire.MsgGetAddr // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddr,
|
||||||
|
msgGetAddrEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgGetAddr
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
msggetblocks.go
Normal file
140
msggetblocks.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
||||||
|
// per message.
|
||||||
|
const MaxBlockLocatorsPerMsg = 500
|
||||||
|
|
||||||
|
// MsgGetBlocks implements the Message interface and represents a bitcoin
|
||||||
|
// getblocks message. It is used to request a list of blocks starting after the
|
||||||
|
// last known hash in the slice of block locator hashes. The list is returned
|
||||||
|
// via an inv message (MsgInv) and is limited by a specific hash to stop at or
|
||||||
|
// the maximum number of blocks per message, which is currently 500.
|
||||||
|
//
|
||||||
|
// Set the HashStop field to the hash at which to stop and use
|
||||||
|
// AddBlockLocatorHash to build up the list of block locator hashes.
|
||||||
|
//
|
||||||
|
// The algorithm for building the block locator hashes should be to add the
|
||||||
|
// hashes in reverse order until you reach the genesis block. In order to keep
|
||||||
|
// the list of locator hashes to a reasonable number of entries, first add the
|
||||||
|
// most recent 10 block hashes, then double the step each loop iteration to
|
||||||
|
// exponentially decrease the number of hashes the further away from head and
|
||||||
|
// closer to the genesis block you get.
|
||||||
|
type MsgGetBlocks struct {
|
||||||
|
ProtocolVersion uint32
|
||||||
|
BlockLocatorHashes []*ShaHash
|
||||||
|
HashStop ShaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockLocatorHash adds a new block locator hash to the message.
|
||||||
|
func (msg *MsgGetBlocks) AddBlockLocatorHash(hash *ShaHash) error {
|
||||||
|
if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "MsgGetBlocks.AddBlockLocatorHash: too many block " +
|
||||||
|
"locator hashes for message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxBlockLocatorsPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readElement(r, &msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num block locator hashes and limit to max.
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "%v: too many block locator hashes in message [%v]"
|
||||||
|
return fmt.Errorf(str, "MsgGetBlocks.BtcDecode", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
sha := ShaHash{}
|
||||||
|
err := readElement(r, &sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(&sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
count := len(msg.BlockLocatorHashes)
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "%v: too many block locator hashes in message [%v]"
|
||||||
|
return fmt.Errorf(str, "MsgGetBlocks.BtcEncode", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hash := range msg.BlockLocatorHashes {
|
||||||
|
err = writeElement(w, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) Command() string {
|
||||||
|
return cmdGetBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetBlocks) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
return 4 + maxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetBlocks returns a new bitcoin getblocks message that conforms to the
|
||||||
|
// Message interface using the passed parameters and defaults for the remaining
|
||||||
|
// fields.
|
||||||
|
func NewMsgGetBlocks(hashStop *ShaHash) *MsgGetBlocks {
|
||||||
|
return &MsgGetBlocks{
|
||||||
|
ProtocolVersion: ProtocolVersion,
|
||||||
|
HashStop: *hashStop,
|
||||||
|
}
|
||||||
|
}
|
259
msggetblocks_test.go
Normal file
259
msggetblocks_test.go
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetBlocks tests the MsgGetBlocks API.
|
||||||
|
func TestGetBlocks(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
locatorHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same data back out.
|
||||||
|
msg := btcwire.NewMsgGetBlocks(hashStop)
|
||||||
|
if !msg.HashStop.IsEqual(hashStop) {
|
||||||
|
t.Errorf("NewMsgGetBlocks: wrong stop hash - got %v, want %v",
|
||||||
|
msg.HashStop, hashStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getblocks"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetBlocks: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
wantPayload := uint32(16045)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure block locator hashes are added properly.
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: %v", err)
|
||||||
|
}
|
||||||
|
if msg.BlockLocatorHashes[0] != locatorHash {
|
||||||
|
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
||||||
|
"got %v, want %v",
|
||||||
|
spew.Sprint(msg.BlockLocatorHashes[0]),
|
||||||
|
spew.Sprint(locatorHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed block locator hashes per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < btcwire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: expected error on too many " +
|
||||||
|
"block locator hashes not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetBlocksWire tests the MsgGetBlocks wire encode and decode for various
|
||||||
|
// numbers of block locator hashes and protocol versions.
|
||||||
|
func TestGetBlocksWire(t *testing.T) {
|
||||||
|
// Set protocol inside getblocks message.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetBlocks message with no block locators or stop hash.
|
||||||
|
NoLocators := btcwire.NewMsgGetBlocks(&btcwire.ShaHash{})
|
||||||
|
NoLocators.ProtocolVersion = pver
|
||||||
|
NoLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x00, // Varint for number of block locator hashes
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetBlocks message with multiple block locators and a stop hash.
|
||||||
|
MultiLocators := btcwire.NewMsgGetBlocks(hashStop)
|
||||||
|
MultiLocators.AddBlockLocatorHash(hashLocator2)
|
||||||
|
MultiLocators.AddBlockLocatorHash(hashLocator)
|
||||||
|
MultiLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgGetBlocks // Message to encode
|
||||||
|
out *btcwire.MsgGetBlocks // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Versionwith multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgGetBlocks
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
msggetdata.go
Normal file
105
msggetdata.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetData implements the Message interface and represents a bitcoin
|
||||||
|
// getdata message. It is used to request data such as blocks and transactions
|
||||||
|
// from another peer. It should be used in response to the inv (MsgInv) message
|
||||||
|
// to request the actual data referenced by each inventory vector the receiving
|
||||||
|
// peer doesn't already have. Each message is limited to a maximum number of
|
||||||
|
// inventory vectors, which is currently 50,000. As a result, multiple messages
|
||||||
|
// must be used to request larger amounts of data.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending a getdata message to another peer.
|
||||||
|
type MsgGetData struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgGetData) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := "MsgAddr.AddAddress: too many invvect in message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxInvPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgGetData.BtcDecode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgGetData.BtcDecode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) Command() string {
|
||||||
|
return cmdGetData
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetData) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return maxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetData returns a new bitcoin getdata message that conforms to the
|
||||||
|
// Message interface. See MsgGetData for details.
|
||||||
|
func NewMsgGetData() *MsgGetData {
|
||||||
|
return &MsgGetData{}
|
||||||
|
}
|
222
msggetdata_test.go
Normal file
222
msggetdata_test.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetData tests the MsgGetData API.
|
||||||
|
func TestGetData(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getdata"
|
||||||
|
msg := btcwire.NewMsgGetData()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetData: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := btcwire.ShaHash{}
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < btcwire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetDataWire tests the MsgGetData wire encode and decode for various
|
||||||
|
// numbers of inventory vectors and protocol versions.
|
||||||
|
func TestGetDataWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash)
|
||||||
|
iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash)
|
||||||
|
|
||||||
|
// Empty MsgGetData message.
|
||||||
|
NoInv := btcwire.NewMsgGetData()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetData message with multiple inventory vectors.
|
||||||
|
MultiInv := btcwire.NewMsgGetData()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvVect_Block
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvVect_Tx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgGetData // Message to encode
|
||||||
|
out *btcwire.MsgGetData // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgGetData
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
msggetheaders.go
Normal file
135
msggetheaders.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgGetHeaders implements the Message interface and represents a bitcoin
|
||||||
|
// getheaders message. It is used to request a list of block headers for
|
||||||
|
// blocks starting after the last known hash in the slice of block locator
|
||||||
|
// hashes. The list is returned via a headers message (MsgHeaders) and is
|
||||||
|
// limited by a specific hash to stop at or the maximum number of block headers
|
||||||
|
// per message, which is currently 2000.
|
||||||
|
//
|
||||||
|
// Set the HashStop field to the hash at which to stop and use
|
||||||
|
// AddBlockLocatorHash to build up the list of block locator hashes.
|
||||||
|
//
|
||||||
|
// The algorithm for building the block locator hashes should be to add the
|
||||||
|
// hashes in reverse order until you reach the genesis block. In order to keep
|
||||||
|
// the list of locator hashes to a resonable number of entries, first add the
|
||||||
|
// most recent 10 block hashes, then double the step each loop iteration to
|
||||||
|
// exponentially decrease the number of hashes the further away from head and
|
||||||
|
// closer to the genesis block you get.
|
||||||
|
type MsgGetHeaders struct {
|
||||||
|
ProtocolVersion uint32
|
||||||
|
BlockLocatorHashes []*ShaHash
|
||||||
|
HashStop ShaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockLocatorHash adds a new block locator hash to the message.
|
||||||
|
func (msg *MsgGetHeaders) AddBlockLocatorHash(hash *ShaHash) error {
|
||||||
|
if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "MsgGetHeaders.AddBlockLocatorHash: too many block " +
|
||||||
|
"locator hashes for message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxBlockLocatorsPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readElement(r, &msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read num block locator hashes and limit to max.
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "%v: too many block locator hashes in message [%v]"
|
||||||
|
return fmt.Errorf(str, "MsgGetHeaders.BtcDecode", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
sha := ShaHash{}
|
||||||
|
err := readElement(r, &sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddBlockLocatorHash(&sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max block locator hashes per message.
|
||||||
|
count := len(msg.BlockLocatorHashes)
|
||||||
|
if count > MaxBlockLocatorsPerMsg {
|
||||||
|
str := "MsgGetHeaders.BtcEncode: too many block locator " +
|
||||||
|
"hashes in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sha := range msg.BlockLocatorHashes {
|
||||||
|
err := writeElement(w, sha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.HashStop)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) Command() string {
|
||||||
|
return cmdGetHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgGetHeaders) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Version 4 bytes + num block locator hashes (varInt) + max allowed block
|
||||||
|
// locators + hash stop.
|
||||||
|
return 4 + maxVarIntPayload + (MaxBlockLocatorsPerMsg * HashSize) + HashSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetHeaders returns a new bitcoin getheaders message that conforms to
|
||||||
|
// the Message interface. See MsgGetHeaders for details.
|
||||||
|
func NewMsgGetHeaders() *MsgGetHeaders {
|
||||||
|
return &MsgGetHeaders{}
|
||||||
|
}
|
248
msggetheaders_test.go
Normal file
248
msggetheaders_test.go
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetHeaders tests the MsgGetHeader API.
|
||||||
|
func TestGetHeaders(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
locatorHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "getheaders"
|
||||||
|
msg := btcwire.NewMsgGetHeaders()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgGetHeaders: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Protocol version 4 bytes + num hashes (varInt) + max block locator
|
||||||
|
// hashes + hash stop.
|
||||||
|
wantPayload := uint32(16045)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure block locator hashes are added properly.
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: %v", err)
|
||||||
|
}
|
||||||
|
if msg.BlockLocatorHashes[0] != locatorHash {
|
||||||
|
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
|
||||||
|
"got %v, want %v",
|
||||||
|
spew.Sprint(msg.BlockLocatorHashes[0]),
|
||||||
|
spew.Sprint(locatorHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed block locator hashes per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < btcwire.MaxBlockLocatorsPerMsg; i++ {
|
||||||
|
err = msg.AddBlockLocatorHash(locatorHash)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddBlockLocatorHash: expected error on too many " +
|
||||||
|
"block locator hashes not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetHeadersWire tests the MsgGetHeaders wire encode and decode for various
|
||||||
|
// numbers of block locator hashes and protocol versions.
|
||||||
|
func TestGetHeadersWire(t *testing.T) {
|
||||||
|
// Set protocol inside getheaders message.
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Block 99499 hash.
|
||||||
|
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
|
||||||
|
hashLocator, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 99500 hash.
|
||||||
|
hashStr = "2e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||||
|
hashLocator2, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hashStop, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetHeaders message with no block locators or stop hash.
|
||||||
|
NoLocators := btcwire.NewMsgGetHeaders()
|
||||||
|
NoLocators.ProtocolVersion = pver
|
||||||
|
NoLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x00, // Varint for number of block locator hashes
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgGetHeaders message with multiple block locators and a stop hash.
|
||||||
|
MultiLocators := btcwire.NewMsgGetHeaders()
|
||||||
|
MultiLocators.ProtocolVersion = pver
|
||||||
|
MultiLocators.HashStop = *hashStop
|
||||||
|
MultiLocators.AddBlockLocatorHash(hashLocator2)
|
||||||
|
MultiLocators.AddBlockLocatorHash(hashLocator)
|
||||||
|
MultiLocatorsEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x02, // Varint for number of block locator hashes
|
||||||
|
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
|
||||||
|
0xd2, 0x20, 0xcc, 0x69, 0x12, 0x83, 0xcb, 0x65,
|
||||||
|
0xbc, 0xaa, 0xe4, 0x79, 0x94, 0xef, 0x9e, 0x7b,
|
||||||
|
0xad, 0xe7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99500 hash
|
||||||
|
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
|
||||||
|
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
|
||||||
|
0x6f, 0x0a, 0x01, 0x3d, 0xc9, 0x7e, 0xc8, 0x40,
|
||||||
|
0x0f, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 99499 hash
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Hash stop
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgGetHeaders // Message to encode
|
||||||
|
out *btcwire.MsgGetHeaders // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Versionwith multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no block locators.
|
||||||
|
{
|
||||||
|
NoLocators,
|
||||||
|
NoLocators,
|
||||||
|
NoLocatorsEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion multiple block locators.
|
||||||
|
{
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocators,
|
||||||
|
MultiLocatorsEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgGetHeaders
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
msgheaders.go
Normal file
119
msgheaders.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxBlockHeadersPerMsg is the maximum number of block headers that can be in
|
||||||
|
// a single bitcoin headers message.
|
||||||
|
const MaxBlockHeadersPerMsg = 2000
|
||||||
|
|
||||||
|
// MsgHeaders implements the Message interface and represents a bitcoin headers
|
||||||
|
// message. It is used to deliver block header information in response
|
||||||
|
// to a getheaders message (MsgGetHeaders). The maximum number of block headers
|
||||||
|
// per message is currently 2000. See MsgGetHeaders for details on requesting
|
||||||
|
// the headers.
|
||||||
|
type MsgHeaders struct {
|
||||||
|
Headers []*BlockHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlockHeader adds a new block header to the message.
|
||||||
|
func (msg *MsgHeaders) AddBlockHeader(bh *BlockHeader) error {
|
||||||
|
if len(msg.Headers)+1 > MaxBlockHeadersPerMsg {
|
||||||
|
str := "MsgHeaders.AddBlockHeader: too many block headers " +
|
||||||
|
"for message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxBlockHeadersPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = append(msg.Headers, bh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max block headers per message.
|
||||||
|
if count > MaxBlockHeadersPerMsg {
|
||||||
|
str := "MsgHeaders.BtcDecode: too many block headers in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
bh := BlockHeader{}
|
||||||
|
err := readBlockHeader(r, pver, &bh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the transaction count is zero for headers.
|
||||||
|
if bh.TxnCount > 0 {
|
||||||
|
str := "MsgHeaders.BtcDecode: block headers may not " +
|
||||||
|
"contain transactions [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
msg.AddBlockHeader(&bh)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max block headers per message.
|
||||||
|
count := len(msg.Headers)
|
||||||
|
if count > MaxBlockHeadersPerMsg {
|
||||||
|
str := "MsgHeaders.BtcEncode: too many block headers in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bh := range msg.Headers {
|
||||||
|
// Ensure block headers do not contain a transaction count.
|
||||||
|
if bh.TxnCount > 0 {
|
||||||
|
str := "MsgHeaders.BtcEncode: block headers " +
|
||||||
|
"may not contain transactions [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeBlockHeader(w, pver, bh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) Command() string {
|
||||||
|
return cmdHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgHeaders) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num headers (varInt) + max allowed headers.
|
||||||
|
return maxVarIntPayload + (maxBlockHeaderPayload * MaxBlockHeadersPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgGetHeaders returns a new bitcoin headers message that conforms to the
|
||||||
|
// Message interface. See MsgHeaders for details.
|
||||||
|
func NewMsgHeaders() *MsgHeaders {
|
||||||
|
return &MsgHeaders{}
|
||||||
|
}
|
219
msgheaders_test.go
Normal file
219
msgheaders_test.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dumpmessage bool = false
|
||||||
|
var checkverbose = true
|
||||||
|
|
||||||
|
const time1 uint32 = 1363020888
|
||||||
|
const time2 uint32 = 1363021079
|
||||||
|
|
||||||
|
// TestHeaders tests the MsgHeaders API.
|
||||||
|
func TestHeaders(t *testing.T) {
|
||||||
|
pver := uint32(60002)
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "headers"
|
||||||
|
msg := btcwire.NewMsgHeaders()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgHeaders: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num headers (varInt) + max allowed headers.
|
||||||
|
wantPayload := uint32(178009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure headers are added properly.
|
||||||
|
bh := &blockOne.Header
|
||||||
|
msg.AddBlockHeader(bh)
|
||||||
|
if !reflect.DeepEqual(msg.Headers[0], bh) {
|
||||||
|
t.Errorf("AddHeader: wrong header - got %v, want %v",
|
||||||
|
spew.Sdump(msg.Headers),
|
||||||
|
spew.Sdump(bh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed headers per message returns
|
||||||
|
// error.
|
||||||
|
var err error
|
||||||
|
for i := 0; i < btcwire.MaxBlockHeadersPerMsg+1; i++ {
|
||||||
|
err = msg.AddBlockHeader(bh)
|
||||||
|
}
|
||||||
|
// TODO(davec): Check for actual error.
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddBlockHeader: expected error on too many headers " +
|
||||||
|
"not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHeadersWire tests the MsgHeaders wire encode and decode for various
|
||||||
|
// numbers of headers and protocol versions.
|
||||||
|
func TestHeadersWire(t *testing.T) {
|
||||||
|
hash := btcwire.GenesisHash
|
||||||
|
merkleHash := blockOne.Header.MerkleRoot
|
||||||
|
bits := uint32(0x1d00ffff)
|
||||||
|
nonce := uint32(0x9962e301)
|
||||||
|
bh := btcwire.NewBlockHeader(&hash, &merkleHash, bits, nonce)
|
||||||
|
bh.Version = blockOne.Header.Version
|
||||||
|
bh.Timestamp = blockOne.Header.Timestamp
|
||||||
|
|
||||||
|
// Empty headers message.
|
||||||
|
noHeaders := btcwire.NewMsgHeaders()
|
||||||
|
noHeadersEncoded := []byte{
|
||||||
|
0x00, // Varint for number of headers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers message with one header.
|
||||||
|
oneHeader := btcwire.NewMsgHeaders()
|
||||||
|
oneHeader.AddBlockHeader(bh)
|
||||||
|
oneHeaderEncoded := []byte{
|
||||||
|
0x01, // VarInt for number of headers.
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||||
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||||
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||||
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||||
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||||
|
0x98, 0x20, 0x51, 0xfd, 0x1e, 0x4b, 0xa7, 0x44,
|
||||||
|
0xbb, 0xbe, 0x68, 0x0e, 0x1f, 0xee, 0x14, 0x67,
|
||||||
|
0x7b, 0xa1, 0xa3, 0xc3, 0x54, 0x0b, 0xf7, 0xb1,
|
||||||
|
0xcd, 0xb6, 0x06, 0xe8, 0x57, 0x23, 0x3e, 0x0e, // MerkleRoot
|
||||||
|
0x61, 0xbc, 0x66, 0x49, // Timestamp
|
||||||
|
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||||
|
0x01, 0xe3, 0x62, 0x99, // Nonce
|
||||||
|
0x00, // TxnCount (0 for headers message)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgHeaders // Message to encode
|
||||||
|
out *btcwire.MsgHeaders // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
// Protocol version NetAddressTimeVersion with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no headers.
|
||||||
|
{
|
||||||
|
noHeaders,
|
||||||
|
noHeaders,
|
||||||
|
noHeadersEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with one header.
|
||||||
|
{
|
||||||
|
oneHeader,
|
||||||
|
oneHeader,
|
||||||
|
oneHeaderEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgHeaders
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
msginv.go
Normal file
104
msginv.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgInv implements the Message interface and represents a bitcoin inv message.
|
||||||
|
// It is used to advertise a peer's known data such as blocks and transactions
|
||||||
|
// through inventory vectors. It may be sent unsolicited to inform other peers
|
||||||
|
// of the data or in response to a getblocks message (MsgGetBlocks). Each
|
||||||
|
// message is limited to a maximum number of inventory vectors, which is
|
||||||
|
// currently 50,000.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending an inv message to another peer.
|
||||||
|
type MsgInv struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgInv) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := "MsgInv.AddAddress: too many invvect in message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxInvPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgInv.BtcDecode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgInv.BtcEncode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) Command() string {
|
||||||
|
return cmdInv
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgInv) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return maxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgInv returns a new bitcoin inv message that conforms to the Message
|
||||||
|
// interface. See MsgInv for details.
|
||||||
|
func NewMsgInv() *MsgInv {
|
||||||
|
return &MsgInv{}
|
||||||
|
}
|
222
msginv_test.go
Normal file
222
msginv_test.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInv tests the MsgInv API.
|
||||||
|
func TestInv(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "inv"
|
||||||
|
msg := btcwire.NewMsgInv()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgInv: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := btcwire.ShaHash{}
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < btcwire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvWire tests the MsgInv wire encode and decode for various numbers
|
||||||
|
// of inventory vectors and protocol versions.
|
||||||
|
func TestInvWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash)
|
||||||
|
iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash)
|
||||||
|
|
||||||
|
// Empty inv message.
|
||||||
|
NoInv := btcwire.NewMsgInv()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inv message with multiple inventory vectors.
|
||||||
|
MultiInv := btcwire.NewMsgInv()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvVect_Block
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvVect_Tx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgInv // Message to encode
|
||||||
|
out *btcwire.MsgInv // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgInv
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
msgmempool.go
Normal file
58
msgmempool.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgMemPool implements the Message interface and represents a bitcoin mempool
|
||||||
|
// message. It is used to request a list of transactions still in the active
|
||||||
|
// memory pool of a relay.
|
||||||
|
//
|
||||||
|
// This message has no payload and was not added until protocol versions
|
||||||
|
// starting with BIP0035Version.
|
||||||
|
type MsgMemPool struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
if pver < BIP0035Version {
|
||||||
|
err := fmt.Errorf("mempool message invalid for protocol version: %d", pver)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if pver < BIP0035Version {
|
||||||
|
err := fmt.Errorf("mempool message invalid for protocol version: %d", pver)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) Command() string {
|
||||||
|
return cmdMemPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgMemPool) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgPong returns a new bitcoin pong message that conforms to the Message
|
||||||
|
// interface. See MsgPong for details.
|
||||||
|
func NewMsgMemPool() *MsgMemPool {
|
||||||
|
return &MsgMemPool{}
|
||||||
|
}
|
65
msgmempool_test.go
Normal file
65
msgmempool_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemPool(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "mempool"
|
||||||
|
msg := btcwire.NewMsgMemPool()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgMemPool: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgMemPool failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older protocol versions should fail encode since message didn't
|
||||||
|
// exist yet.
|
||||||
|
oldPver := btcwire.BIP0035Version - 1
|
||||||
|
err = msg.BtcEncode(&buf, oldPver)
|
||||||
|
if err == nil {
|
||||||
|
s := "encode of MsgMemPool passed for old protocol version %v err <%v>"
|
||||||
|
t.Errorf(s, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := btcwire.NewMsgMemPool()
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgMemPool failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older protocol versions should fail decode since message didn't
|
||||||
|
// exist yet.
|
||||||
|
err = readmsg.BtcDecode(&buf, oldPver)
|
||||||
|
if err == nil {
|
||||||
|
s := "decode of MsgMemPool passed for old protocol version %v err <%v>"
|
||||||
|
t.Errorf(s, msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
103
msgnotfound.go
Normal file
103
msgnotfound.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgNotFound defines a bitcoin notfound message which is sent in response to
|
||||||
|
// a getdata message if any of the requested data in not available on the peer.
|
||||||
|
// Each message is limited to a maximum number of inventory vectors, which is
|
||||||
|
// currently 50,000.
|
||||||
|
//
|
||||||
|
// Use the AddInvVect function to build up the list of inventory vectors when
|
||||||
|
// sending a notfound message to another peer.
|
||||||
|
type MsgNotFound struct {
|
||||||
|
InvList []*InvVect
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvVect adds an inventory vector to the message.
|
||||||
|
func (msg *MsgNotFound) AddInvVect(iv *InvVect) error {
|
||||||
|
if len(msg.InvList)+1 > MaxInvPerMsg {
|
||||||
|
str := "MsgNotFound.AddAddress: too many invvect in message [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxInvPerMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.InvList = append(msg.InvList, iv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgNotFound.BtcDecode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
iv := InvVect{}
|
||||||
|
err := readInvVect(r, pver, &iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.AddInvVect(&iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// Limit to max inventory vectors per message.
|
||||||
|
count := len(msg.InvList)
|
||||||
|
if count > MaxInvPerMsg {
|
||||||
|
str := "MsgNotFound.BtcEncode: too many invvect in message [%v]"
|
||||||
|
return fmt.Errorf(str, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeVarInt(w, pver, uint64(count))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iv := range msg.InvList {
|
||||||
|
err := writeInvVect(w, pver, iv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) Command() string {
|
||||||
|
return cmdNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgNotFound) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// Max var int 9 bytes + max InvVects at 36 bytes each.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
return maxVarIntPayload + (MaxInvPerMsg * maxInvVectPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgNotFound returns a new bitcoin notfound message that conforms to the
|
||||||
|
// Message interface. See MsgNotFound for details.
|
||||||
|
func NewMsgNotFound() *MsgNotFound {
|
||||||
|
return &MsgNotFound{}
|
||||||
|
}
|
222
msgnotfound_test.go
Normal file
222
msgnotfound_test.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNotFound tests the MsgNotFound API.
|
||||||
|
func TestNotFound(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "notfound"
|
||||||
|
msg := btcwire.NewMsgNotFound()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgNotFound: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num inventory vectors (varInt) + max allowed inventory vectors.
|
||||||
|
wantPayload := uint32(1800009)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure inventory vectors are added properly.
|
||||||
|
hash := btcwire.ShaHash{}
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, &hash)
|
||||||
|
err := msg.AddInvVect(iv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("AddInvVect: %v", err)
|
||||||
|
}
|
||||||
|
if msg.InvList[0] != iv {
|
||||||
|
t.Errorf("AddInvVect: wrong invvect added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.InvList[0]), spew.Sprint(iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding more than the max allowed inventory vectors per
|
||||||
|
// message returns an error.
|
||||||
|
for i := 0; i < btcwire.MaxInvPerMsg; i++ {
|
||||||
|
err = msg.AddInvVect(iv)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("AddInvVect: expected error on too many inventory " +
|
||||||
|
"vectors not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNotFoundWire tests the MsgNotFound wire encode and decode for various
|
||||||
|
// numbers of inventory vectors and protocol versions.
|
||||||
|
func TestNotFoundWire(t *testing.T) {
|
||||||
|
// Block 203707 hash.
|
||||||
|
hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc"
|
||||||
|
blockHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transation 1 of Block 203707 hash.
|
||||||
|
hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0"
|
||||||
|
txHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash)
|
||||||
|
iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash)
|
||||||
|
|
||||||
|
// Empty notfound message.
|
||||||
|
NoInv := btcwire.NewMsgNotFound()
|
||||||
|
NoInvEncoded := []byte{
|
||||||
|
0x00, // Varint for number of inventory vectors
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFound message with multiple inventory vectors.
|
||||||
|
MultiInv := btcwire.NewMsgNotFound()
|
||||||
|
MultiInv.AddInvVect(iv)
|
||||||
|
MultiInv.AddInvVect(iv2)
|
||||||
|
MultiInvEncoded := []byte{
|
||||||
|
0x02, // Varint for number of inv vectors
|
||||||
|
0x02, 0x00, 0x00, 0x00, // InvVect_Block
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InvVect_Tx
|
||||||
|
0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf,
|
||||||
|
0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8,
|
||||||
|
0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98,
|
||||||
|
0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgNotFound // Message to encode
|
||||||
|
out *btcwire.MsgNotFound // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion no inv vectors.
|
||||||
|
{
|
||||||
|
NoInv,
|
||||||
|
NoInv,
|
||||||
|
NoInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple inv vectors.
|
||||||
|
{
|
||||||
|
MultiInv,
|
||||||
|
MultiInv,
|
||||||
|
MultiInvEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgNotFound
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
msgping.go
Normal file
87
msgping.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgPong implements the Message interface and represents a bitcoin ping
|
||||||
|
// message.
|
||||||
|
//
|
||||||
|
// For versions BIP0031Version and earlier, it is used primarily to confirm
|
||||||
|
// that a connection is still valid. A transmission error is typically
|
||||||
|
// interpreted as a closed connection and that the peer should be removed.
|
||||||
|
// For versions AFTER BIP0031Version it contains an identifier which can be
|
||||||
|
// returned in the pong message to determine network timing.
|
||||||
|
//
|
||||||
|
// The payload for this message just consists of a nonce used for identifying
|
||||||
|
// it later.
|
||||||
|
type MsgPing struct {
|
||||||
|
// Unique value associated with message that is used to identify
|
||||||
|
// specific ping message.
|
||||||
|
Nonce uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
err := readElement(r, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
err := writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) Command() string {
|
||||||
|
return cmdPing
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPing) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
plen := uint32(0)
|
||||||
|
// There was no nonce for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
// Nonce 8 bytes.
|
||||||
|
plen += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgPing returns a new bitcoin ping message that conforms to the Message
|
||||||
|
// interface. See MsgPing for details.
|
||||||
|
func NewMsgPing(nonce uint64) *MsgPing {
|
||||||
|
return &MsgPing{
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
}
|
194
msgping_test.go
Normal file
194
msgping_test.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPing tests the MsgPing API against the latest protocol version.
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure we get the same nonce back out.
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "ping"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgPing: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(8)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingBIP0031 tests the MsgPing API against the protocol version
|
||||||
|
// BIP0031Version.
|
||||||
|
func TestPingBIP0031(t *testing.T) {
|
||||||
|
// Use the protocol version just prior to BIP0031Version changes.
|
||||||
|
pver := btcwire.BIP0031Version
|
||||||
|
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for old protocol version.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with old protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPing failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with old protocol version.
|
||||||
|
readmsg := btcwire.NewMsgPing(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this protocol version doesn't support the nonce, make sure
|
||||||
|
// it didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Errorf("Should not get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingCrossProtocol tests the MsgPing API when encoding with the latest
|
||||||
|
// protocol version and decoded with BIP0031Version.
|
||||||
|
func TestPingCrossProtocol(t *testing.T) {
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPing(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPing: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, btcwire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPing failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
readmsg := btcwire.NewMsgPing(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the nonce, make
|
||||||
|
// sure it didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Error("Should not get same nonce for cross protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPingWire tests the MsgPing wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestPingWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.MsgPing // Message to encode
|
||||||
|
out btcwire.MsgPing // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
btcwire.MsgPing{Nonce: 123123}, // 0x1e0f3
|
||||||
|
btcwire.MsgPing{Nonce: 123123}, // 0x1e0f3
|
||||||
|
[]byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version+1
|
||||||
|
{
|
||||||
|
btcwire.MsgPing{Nonce: 456456}, // 0x6f708
|
||||||
|
btcwire.MsgPing{Nonce: 456456}, // 0x6f708
|
||||||
|
[]byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
btcwire.BIP0031Version + 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version
|
||||||
|
{
|
||||||
|
btcwire.MsgPing{Nonce: 789789}, // 0xc0d1d
|
||||||
|
btcwire.MsgPing{Nonce: 0}, // No nonce for pver
|
||||||
|
[]byte{}, // No nonce for pver
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgPing
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
msgpong.go
Normal file
88
msgpong.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgPong implements the Message interface and represents a bitcoin pong
|
||||||
|
// message which is used primarily to confirm that a connection is still valid
|
||||||
|
// in response to a bitcoin ping message (MsgPing).
|
||||||
|
//
|
||||||
|
// This message was not added until protocol versions AFTER BIP0031Version.
|
||||||
|
type MsgPong struct {
|
||||||
|
// Unique value associated with message that is used to identify
|
||||||
|
// specific ping message.
|
||||||
|
Nonce uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
// NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver <= BIP0031Version {
|
||||||
|
err := fmt.Errorf("pong message invalid for protocol version: %d",
|
||||||
|
pver)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := readElement(r, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
// NOTE: <= is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver <= BIP0031Version {
|
||||||
|
err := fmt.Errorf("pong message invalid for protocol version: %d",
|
||||||
|
pver)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) Command() string {
|
||||||
|
return cmdPong
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgPong) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
plen := uint32(0)
|
||||||
|
// The pong message did not exist for BIP0031Version and earlier.
|
||||||
|
// NOTE: > is not a mistake here. The BIP0031 was defined as AFTER
|
||||||
|
// the version unlike most others.
|
||||||
|
if pver > BIP0031Version {
|
||||||
|
// Nonce 8 bytes.
|
||||||
|
plen += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgPong returns a new bitcoin pong message that conforms to the Message
|
||||||
|
// interface. See MsgPong for details.
|
||||||
|
func NewMsgPong(nonce uint64) *MsgPong {
|
||||||
|
return &MsgPong{
|
||||||
|
Nonce: nonce,
|
||||||
|
}
|
||||||
|
}
|
215
msgpong_test.go
Normal file
215
msgpong_test.go
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPongLatest tests the MsgPong API against the latest protocol version.
|
||||||
|
func TestPongLatest(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgPong: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "pong"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgPong: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
wantPayload := uint32(8)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPong failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with latest protocol version.
|
||||||
|
readmsg := btcwire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decode of MsgPong failed [%v] err <%v>", buf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure nonce is the same.
|
||||||
|
if msg.Nonce != readmsg.Nonce {
|
||||||
|
t.Errorf("Should get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongBIP0031 tests the MsgPong API against the protocol version
|
||||||
|
// BIP0031Version.
|
||||||
|
func TestPongBIP0031(t *testing.T) {
|
||||||
|
// Use the protocol version just prior to BIP0031Version changes.
|
||||||
|
pver := btcwire.BIP0031Version
|
||||||
|
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("Should get same nonce back out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for old protocol version.
|
||||||
|
size := msg.MaxPayloadLength(pver)
|
||||||
|
if size != 0 {
|
||||||
|
t.Errorf("Max length should be 0 for pong protocol version %d.",
|
||||||
|
pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test encode with old protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgPong succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test decode with old protocol version.
|
||||||
|
readmsg := btcwire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, pver)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("decode of MsgPong succeeded when it shouldn't have",
|
||||||
|
spew.Sdump(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since this protocol version doesn't support pong, make sure the
|
||||||
|
// nonce didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Errorf("Should not get same nonce for protocol version %d", pver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongCrossProtocol tests the MsgPong API when encoding with the latest
|
||||||
|
// protocol version and decoded with BIP0031Version.
|
||||||
|
func TestPongCrossProtocol(t *testing.T) {
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
msg := btcwire.NewMsgPong(nonce)
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("Should get same nonce back out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode with latest protocol version.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = msg.BtcEncode(&buf, btcwire.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("encode of MsgPong failed %v err <%v>", msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode with old protocol version.
|
||||||
|
readmsg := btcwire.NewMsgPong(0)
|
||||||
|
err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("encode of MsgPong succeeded when it shouldn't have %v",
|
||||||
|
msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since one of the protocol versions doesn't support the pong message,
|
||||||
|
// make sure the nonce didn't get encoded and decoded back out.
|
||||||
|
if msg.Nonce == readmsg.Nonce {
|
||||||
|
t.Error("Should not get same nonce for cross protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPongWire tests the MsgPong wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestPongWire(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.MsgPong // Message to encode
|
||||||
|
out btcwire.MsgPong // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
err error // expected error
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
btcwire.MsgPong{Nonce: 123123}, // 0x1e0f3
|
||||||
|
btcwire.MsgPong{Nonce: 123123}, // 0x1e0f3
|
||||||
|
[]byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version+1
|
||||||
|
{
|
||||||
|
btcwire.MsgPong{Nonce: 456456}, // 0x6f708
|
||||||
|
btcwire.MsgPong{Nonce: 456456}, // 0x6f708
|
||||||
|
[]byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
|
btcwire.BIP0031Version + 1,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version
|
||||||
|
//{
|
||||||
|
// btcwire.MsgPong{Nonce: 789789}, // 0xc0d1d
|
||||||
|
// btcwire.MsgPong{Nonce: 0}, // No nonce for pver
|
||||||
|
// []byte{}, // No nonce for pver
|
||||||
|
// btcwire.BIP0031Version,
|
||||||
|
// nil, /// Need err type....
|
||||||
|
//},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgPong
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
387
msgtx.go
Normal file
387
msgtx.go
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||||
|
// of a transaction input can be.
|
||||||
|
const MaxTxInSequenceNum uint32 = 0xffffffff
|
||||||
|
|
||||||
|
// Outpoint defines a bitcoin data type that is used to track previous
|
||||||
|
// transaction outputs.
|
||||||
|
type OutPoint struct {
|
||||||
|
Hash ShaHash
|
||||||
|
Index uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutPoint returns a new bitcoin transaction outpoint point with the
|
||||||
|
// provided hash and index.
|
||||||
|
func NewOutPoint(hash *ShaHash, index uint32) *OutPoint {
|
||||||
|
return &OutPoint{
|
||||||
|
Hash: *hash,
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxIn defines a bitcoin transaction input.
|
||||||
|
type TxIn struct {
|
||||||
|
PreviousOutpoint OutPoint
|
||||||
|
SignatureScript []byte
|
||||||
|
Sequence uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxIn returns a new bitcoin transaction input with the provided
|
||||||
|
// previous outpoint point and signature script with a default sequence of
|
||||||
|
// MaxTxInSequenceNum.
|
||||||
|
func NewTxIn(prevOut *OutPoint, signatureScript []byte) *TxIn {
|
||||||
|
return &TxIn{
|
||||||
|
PreviousOutpoint: *prevOut,
|
||||||
|
SignatureScript: signatureScript,
|
||||||
|
Sequence: MaxTxInSequenceNum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxOut defines a bitcoin transaction output.
|
||||||
|
type TxOut struct {
|
||||||
|
Value int64
|
||||||
|
PkScript []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxOut returns a new bitcoin transaction output with the provided
|
||||||
|
// transaction value and public key script.
|
||||||
|
func NewTxOut(value int64, pkScript []byte) *TxOut {
|
||||||
|
return &TxOut{
|
||||||
|
Value: value,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgTx implements the Message interface and represents a bitcoin tx message.
|
||||||
|
// It is used to deliver transaction information in response to a getdata
|
||||||
|
// message (MsgGetData) for a given transaction.
|
||||||
|
//
|
||||||
|
// Use the AddTxIn and AddTxOut functions to build up the list of transaction
|
||||||
|
// inputs and outputs.
|
||||||
|
type MsgTx struct {
|
||||||
|
Version uint32
|
||||||
|
TxIn []*TxIn
|
||||||
|
TxOut []*TxOut
|
||||||
|
LockTime uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxIn adds a transaction input to the message.
|
||||||
|
func (msg *MsgTx) AddTxIn(ti *TxIn) {
|
||||||
|
msg.TxIn = append(msg.TxIn, ti)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxOut adds a transaction output to the message.
|
||||||
|
func (msg *MsgTx) AddTxOut(to *TxOut) {
|
||||||
|
msg.TxOut = append(msg.TxOut, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxSha generates the ShaHash name for the transaction.
|
||||||
|
func (tx *MsgTx) TxSha(pver uint32) (ShaHash, error) {
|
||||||
|
var txsha ShaHash
|
||||||
|
var wbuf bytes.Buffer
|
||||||
|
err := tx.BtcEncode(&wbuf, pver)
|
||||||
|
if err != nil {
|
||||||
|
return txsha, err
|
||||||
|
}
|
||||||
|
txsha.SetBytes(DoubleSha256(wbuf.Bytes()))
|
||||||
|
|
||||||
|
return txsha, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of a transaction so that the original does not get
|
||||||
|
// modified when the copy is manipulated.
|
||||||
|
func (tx *MsgTx) Copy() *MsgTx {
|
||||||
|
// Create new tx and start by copying primitive values.
|
||||||
|
newTx := MsgTx{
|
||||||
|
Version: tx.Version,
|
||||||
|
LockTime: tx.LockTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy the old TxIn data.
|
||||||
|
for _, oldTxIn := range tx.TxIn {
|
||||||
|
// Deep copy the old previous outpoint.
|
||||||
|
oldOutPoint := oldTxIn.PreviousOutpoint
|
||||||
|
newOutPoint := OutPoint{}
|
||||||
|
newOutPoint.Hash.SetBytes(oldOutPoint.Hash[:])
|
||||||
|
newOutPoint.Index = oldOutPoint.Index
|
||||||
|
|
||||||
|
// Deep copy the old signature script.
|
||||||
|
var newScript []byte
|
||||||
|
oldScript := oldTxIn.SignatureScript
|
||||||
|
oldScriptLen := len(oldScript)
|
||||||
|
if oldScriptLen > 0 {
|
||||||
|
newScript = make([]byte, oldScriptLen, oldScriptLen)
|
||||||
|
copy(newScript, oldScript[:oldScriptLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new txIn with the deep copied data and append it to
|
||||||
|
// new Tx.
|
||||||
|
newTxIn := TxIn{
|
||||||
|
PreviousOutpoint: newOutPoint,
|
||||||
|
SignatureScript: newScript,
|
||||||
|
Sequence: oldTxIn.Sequence,
|
||||||
|
}
|
||||||
|
newTx.TxIn = append(newTx.TxIn, &newTxIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy the old TxOut data.
|
||||||
|
for _, oldTxOut := range tx.TxOut {
|
||||||
|
// Deep copy the old PkScript
|
||||||
|
var newScript []byte
|
||||||
|
oldScript := oldTxOut.PkScript
|
||||||
|
oldScriptLen := len(oldScript)
|
||||||
|
if oldScriptLen > 0 {
|
||||||
|
newScript = make([]byte, oldScriptLen, oldScriptLen)
|
||||||
|
copy(newScript, oldScript[:oldScriptLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new txOut with the deep copied data and append it to
|
||||||
|
// new Tx.
|
||||||
|
newTxOut := TxOut{
|
||||||
|
Value: oldTxOut.Value,
|
||||||
|
PkScript: newScript,
|
||||||
|
}
|
||||||
|
newTx.TxOut = append(newTx.TxOut, &newTxOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
err := readElement(r, &msg.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
ti := TxIn{}
|
||||||
|
err = readTxIn(r, pver, msg.Version, &ti)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.TxIn = append(msg.TxIn, &ti)
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
to := TxOut{}
|
||||||
|
err = readTxOut(r, pver, msg.Version, &to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.TxOut = append(msg.TxOut, &to)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.LockTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
err := writeElement(w, msg.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := uint64(len(msg.TxIn))
|
||||||
|
err = writeVarInt(w, pver, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ti := range msg.TxIn {
|
||||||
|
err = writeTxIn(w, pver, msg.Version, ti)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count = uint64(len(msg.TxOut))
|
||||||
|
err = writeVarInt(w, pver, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, to := range msg.TxOut {
|
||||||
|
err = writeTxOut(w, pver, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.LockTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) Command() string {
|
||||||
|
return cmdTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return maxMessagePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgTx returns a new bitcoin tx message that conforms to the Message
|
||||||
|
// interface. The return instance has a default version of TxVersion and there
|
||||||
|
// are no transaction inputs or outputs. Also, the lock time is set to zero
|
||||||
|
// to indicate the transaction is valid immediately as opposed to some time in
|
||||||
|
// future.
|
||||||
|
func NewMsgTx() *MsgTx {
|
||||||
|
return &MsgTx{Version: TxVersion}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readOutPoint reads the next sequence of bytes from r as an OutPoint.
|
||||||
|
func readOutPoint(r io.Reader, pver uint32, version uint32, op *OutPoint) error {
|
||||||
|
err := readElements(r, &op.Hash, &op.Index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOutPoint encodes op to the bitcoin protocol encoding for an OutPoint
|
||||||
|
// to w.
|
||||||
|
func writeOutPoint(w io.Writer, pver uint32, version uint32, op *OutPoint) error {
|
||||||
|
err := writeElements(w, op.Hash, op.Index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTxIn reads the next sequence of bytes from r as a transaction input
|
||||||
|
// (TxIn).
|
||||||
|
func readTxIn(r io.Reader, pver uint32, version uint32, ti *TxIn) error {
|
||||||
|
op := OutPoint{}
|
||||||
|
err := readOutPoint(r, pver, version, &op)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ti.PreviousOutpoint = op
|
||||||
|
|
||||||
|
count, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, count)
|
||||||
|
err = readElement(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ti.SignatureScript = b
|
||||||
|
|
||||||
|
err = readElement(r, &ti.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTxIn encodes ti to the bitcoin protocol encoding for a transaction
|
||||||
|
// input (TxIn) to w.
|
||||||
|
func writeTxIn(w io.Writer, pver uint32, version uint32, ti *TxIn) error {
|
||||||
|
err := writeOutPoint(w, pver, version, &ti.PreviousOutpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slen := uint64(len(ti.SignatureScript))
|
||||||
|
err = writeVarInt(w, pver, slen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(ti.SignatureScript)
|
||||||
|
_, err = w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, &ti.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTxOut reads the next sequence of bytes from r as a transaction output
|
||||||
|
// (TxOut).
|
||||||
|
func readTxOut(r io.Reader, pver uint32, version uint32, to *TxOut) error {
|
||||||
|
err := readElement(r, &to.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slen, err := readVarInt(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, slen)
|
||||||
|
err = readElement(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to.PkScript = b
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTxOut encodes to into the bitcoin protocol encoding for a transaction
|
||||||
|
// output (TxOut) to w.
|
||||||
|
func writeTxOut(w io.Writer, pver uint32, to *TxOut) error {
|
||||||
|
err := writeElement(w, to.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkLen := uint64(len(to.PkScript))
|
||||||
|
err = writeVarInt(w, pver, pkLen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, to.PkScript)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
361
msgtx_test.go
Normal file
361
msgtx_test.go
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestTx tests the MsgTx API.
|
||||||
|
func TestTx(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Block 100000 hash.
|
||||||
|
hashStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "tx"
|
||||||
|
msg := btcwire.NewMsgTx()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgAddr: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
// Num addresses (varInt) + max allowed addresses.
|
||||||
|
wantPayload := uint32(1024 * 1024 * 32)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction output point data back out.
|
||||||
|
prevOutIndex := uint32(1)
|
||||||
|
prevOut := btcwire.NewOutPoint(hash, prevOutIndex)
|
||||||
|
if !prevOut.Hash.IsEqual(hash) {
|
||||||
|
t.Errorf("NewOutPoint: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(&prevOut.Hash), spew.Sprint(hash))
|
||||||
|
}
|
||||||
|
if prevOut.Index != prevOutIndex {
|
||||||
|
t.Errorf("NewOutPoint: wrong index - got %v, want %v",
|
||||||
|
prevOut.Index, prevOutIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction input back out.
|
||||||
|
sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}
|
||||||
|
txIn := btcwire.NewTxIn(prevOut, sigScript)
|
||||||
|
if !reflect.DeepEqual(&txIn.PreviousOutpoint, prevOut) {
|
||||||
|
t.Errorf("NewTxIn: wrong prev outpoint - got %v, want %v",
|
||||||
|
spew.Sprint(&txIn.PreviousOutpoint),
|
||||||
|
spew.Sprint(prevOut))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(txIn.SignatureScript, sigScript) {
|
||||||
|
t.Errorf("NewTxIn: wrong signature script - got %v, want %v",
|
||||||
|
spew.Sdump(txIn.SignatureScript),
|
||||||
|
spew.Sdump(sigScript))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same transaction output back out.
|
||||||
|
txValue := int64(5000000000)
|
||||||
|
pkScript := []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
}
|
||||||
|
txOut := btcwire.NewTxOut(txValue, pkScript)
|
||||||
|
if txOut.Value != txValue {
|
||||||
|
t.Errorf("NewTxOut: wrong pk script - got %v, want %v",
|
||||||
|
txOut.Value, txValue)
|
||||||
|
|
||||||
|
}
|
||||||
|
if !bytes.Equal(txOut.PkScript, pkScript) {
|
||||||
|
t.Errorf("NewTxOut: wrong pk script - got %v, want %v",
|
||||||
|
spew.Sdump(txOut.PkScript),
|
||||||
|
spew.Sdump(pkScript))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transaction inputs are added properly.
|
||||||
|
msg.AddTxIn(txIn)
|
||||||
|
if !reflect.DeepEqual(msg.TxIn[0], txIn) {
|
||||||
|
t.Errorf("AddTxIn: wrong transaction input added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.TxIn[0]), spew.Sprint(txIn))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transaction outputs are added properly.
|
||||||
|
msg.AddTxOut(txOut)
|
||||||
|
if !reflect.DeepEqual(msg.TxOut[0], txOut) {
|
||||||
|
t.Errorf("AddTxIn: wrong transaction output added - got %v, want %v",
|
||||||
|
spew.Sprint(msg.TxOut[0]), spew.Sprint(txOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the copy produced an identical transaction message.
|
||||||
|
newMsg := msg.Copy()
|
||||||
|
if !reflect.DeepEqual(newMsg, msg) {
|
||||||
|
t.Errorf("Copy: mismatched tx messages - got %v, want %v",
|
||||||
|
spew.Sdump(newMsg), spew.Sdump(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTxSha(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Hash of first transaction from block 113875.
|
||||||
|
hashStr := "f051e59b5e2503ac626d03aaeac8ab7be2d72ba4b7e97119c5852d70d52dcb86"
|
||||||
|
wantHash, err := btcwire.NewShaHashFromStr(hashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First transaction from block 113875.
|
||||||
|
msgTx := btcwire.NewMsgTx()
|
||||||
|
txIn := btcwire.TxIn{
|
||||||
|
PreviousOutpoint: btcwire.OutPoint{
|
||||||
|
Hash: btcwire.ShaHash{0x00},
|
||||||
|
Index: 0xffffffff,
|
||||||
|
},
|
||||||
|
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
||||||
|
Sequence: 0xffffffff,
|
||||||
|
}
|
||||||
|
txOut := btcwire.TxOut{
|
||||||
|
Value: 5000000000,
|
||||||
|
PkScript: []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
},
|
||||||
|
}
|
||||||
|
msgTx.AddTxIn(&txIn)
|
||||||
|
msgTx.AddTxOut(&txOut)
|
||||||
|
msgTx.LockTime = 0
|
||||||
|
|
||||||
|
// Ensure the hash produced is expected.
|
||||||
|
txHash, err := msgTx.TxSha(pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TxSha: %v", err)
|
||||||
|
}
|
||||||
|
if !txHash.IsEqual(wantHash) {
|
||||||
|
t.Errorf("TxSha: wrong hash - got %v, want %v",
|
||||||
|
spew.Sprint(txHash), spew.Sprint(wantHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTxWire tests the MsgTx wire encode and decode for various numbers
|
||||||
|
// of transaction inputs and outputs and protocol versions.
|
||||||
|
func TestTxWire(t *testing.T) {
|
||||||
|
// Previous transaction output point for coinbase to test.
|
||||||
|
prevOutIndex := uint32(0xffffffff)
|
||||||
|
prevOut := btcwire.NewOutPoint(&btcwire.ShaHash{}, prevOutIndex)
|
||||||
|
|
||||||
|
// Transaction input to test.
|
||||||
|
sigScript := []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}
|
||||||
|
txIn := btcwire.NewTxIn(prevOut, sigScript)
|
||||||
|
txIn.Sequence = 0xffffffff
|
||||||
|
|
||||||
|
// Transaction output to test.
|
||||||
|
txValue := int64(5000000000)
|
||||||
|
pkScript := []byte{
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
}
|
||||||
|
txOut := btcwire.NewTxOut(txValue, pkScript)
|
||||||
|
|
||||||
|
// Empty tx message.
|
||||||
|
noTx := btcwire.NewMsgTx()
|
||||||
|
noTx.Version = 1
|
||||||
|
noTxEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x00, // Varint for number of input transactions
|
||||||
|
0x00, // Varint for number of output transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
|
||||||
|
multiTx := btcwire.NewMsgTx()
|
||||||
|
multiTx.Version = 1
|
||||||
|
multiTx.AddTxIn(txIn)
|
||||||
|
multiTx.AddTxOut(txOut)
|
||||||
|
multiTx.LockTime = 0
|
||||||
|
multiTxEncoded := []byte{
|
||||||
|
0x01, 0x00, 0x00, 0x00, // Version
|
||||||
|
0x01, // Varint for number of input transactions
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Previous output hash
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||||
|
0x07, // Varint for length of signature script
|
||||||
|
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script
|
||||||
|
0xff, 0xff, 0xff, 0xff, // Sequence
|
||||||
|
0x01, // Varint for number of output transactions
|
||||||
|
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||||
|
0x43, // Varint for length of pk script
|
||||||
|
0x41, // OP_DATA_65
|
||||||
|
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||||
|
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||||
|
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||||
|
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||||
|
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||||
|
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||||
|
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||||
|
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||||
|
0xa6, // 65-byte signature
|
||||||
|
0xac, // OP_CHECKSIG
|
||||||
|
0x00, 0x00, 0x00, 0x00, // Lock time
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgTx // Message to encode
|
||||||
|
out *btcwire.MsgTx // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with no transactions.
|
||||||
|
{
|
||||||
|
noTx,
|
||||||
|
noTx,
|
||||||
|
noTxEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion with multiple transactions.
|
||||||
|
{
|
||||||
|
multiTx,
|
||||||
|
multiTx,
|
||||||
|
multiTxEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgTx
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
msgverack.go
Normal file
46
msgverack.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgVerAck defines a bitcoin verack message which is used for a peer to
|
||||||
|
// acknowledge a version message (MsgVersion) after it has used the information
|
||||||
|
// to negotiate parameters. It implements the Message interface.
|
||||||
|
//
|
||||||
|
// This message has no payload.
|
||||||
|
type MsgVerAck struct{}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) Command() string {
|
||||||
|
return cmdVerAck
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVerAck) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVerAck returns a new bitcoin verack message that conforms to the
|
||||||
|
// Message interface.
|
||||||
|
func NewMsgVerAck() *MsgVerAck {
|
||||||
|
return &MsgVerAck{}
|
||||||
|
}
|
121
msgverack_test.go
Normal file
121
msgverack_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestVerAck tests the MsgVerAck API.
|
||||||
|
func TestVerAck(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "verack"
|
||||||
|
msg := btcwire.NewMsgVerAck()
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgVerAck: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
wantPayload := uint32(0)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVerAckWire tests the MsgVerAck wire encode and decode for various
|
||||||
|
// protocol versions.
|
||||||
|
func TestVerAckWire(t *testing.T) {
|
||||||
|
msgVerAck := btcwire.NewMsgVerAck()
|
||||||
|
msgVerAckEncoded := []byte{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgVerAck // Message to encode
|
||||||
|
out *btcwire.MsgVerAck // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAck,
|
||||||
|
msgVerAckEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgVerAck
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
210
msgversion.go
Normal file
210
msgversion.go
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
||||||
|
// version message (MsgVersion).
|
||||||
|
const MaxUserAgentLen = 2000
|
||||||
|
|
||||||
|
// MsgVersion implements the Message interface and represents a bitcoin version
|
||||||
|
// message. It is used for a peer to advertise itself as soon as an outbound
|
||||||
|
// connection is made. The remote peer then uses this information along with
|
||||||
|
// its own to negotiate. The remote peer must then respond with a version
|
||||||
|
// message of its own containing the negotiated values followed by a verack
|
||||||
|
// message (MsgVerAck). This exchange must take place before any further
|
||||||
|
// communication is allowed to proceed.
|
||||||
|
type MsgVersion struct {
|
||||||
|
// Version of the protocol the node is using.
|
||||||
|
ProtocolVersion int32
|
||||||
|
|
||||||
|
// Bitfield which identifies the enabled services.
|
||||||
|
Services ServiceFlag
|
||||||
|
|
||||||
|
// Time the message was generated. This is encoded as an int64 on the wire.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Address of the remote peer.
|
||||||
|
AddrYou NetAddress
|
||||||
|
|
||||||
|
// Address of the local peer.
|
||||||
|
AddrMe NetAddress
|
||||||
|
|
||||||
|
// Unique value associated with message that is used to detect self
|
||||||
|
// connections.
|
||||||
|
Nonce uint64
|
||||||
|
|
||||||
|
// The user agent that generated messsage. This is a encoded as a varString
|
||||||
|
// on the wire. This has a max length of MaxUserAgentLen.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Last block seen by the generator of the version message.
|
||||||
|
LastBlock int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasService returns whether the specified service is supported by the peer
|
||||||
|
// that generated the message.
|
||||||
|
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
|
||||||
|
if msg.Services&service == service {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddService adds service as a supported service by the peer generating the
|
||||||
|
// message.
|
||||||
|
func (msg *MsgVersion) AddService(service ServiceFlag) {
|
||||||
|
msg.Services |= service
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error {
|
||||||
|
var sec int64
|
||||||
|
err := readElements(r, &msg.ProtocolVersion, &msg.Services, &sec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Timestamp = time.Unix(sec, 0)
|
||||||
|
|
||||||
|
err = readNetAddress(r, pver, &msg.AddrYou, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readNetAddress(r, pver, &msg.AddrMe, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = readElement(r, &msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent, err := readVarString(r, pver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(userAgent) > MaxUserAgentLen {
|
||||||
|
str := "MsgVersion.BtcDecode: user agent too long [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxUserAgentLen)
|
||||||
|
}
|
||||||
|
msg.UserAgent = userAgent
|
||||||
|
|
||||||
|
err = readElement(r, &msg.LastBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
|
||||||
|
// This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
|
||||||
|
if len(msg.UserAgent) > MaxUserAgentLen {
|
||||||
|
str := "MsgVersion.BtcEncode: user agent too long [max %v]"
|
||||||
|
return fmt.Errorf(str, MaxUserAgentLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeElements(w, msg.ProtocolVersion, msg.Services,
|
||||||
|
msg.Timestamp.Unix())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeNetAddress(w, pver, &msg.AddrYou, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeNetAddress(w, pver, &msg.AddrMe, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.Nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeVarString(w, pver, msg.UserAgent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeElement(w, msg.LastBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message. This is part
|
||||||
|
// of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) Command() string {
|
||||||
|
return cmdVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
||||||
|
// receiver. This is part of the Message interface implementation.
|
||||||
|
func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 {
|
||||||
|
// XXX: <= 106 different
|
||||||
|
// XXX: >= 70001 different
|
||||||
|
|
||||||
|
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes + remote
|
||||||
|
// and local net addresses + nonce 8 bytes + length of user agent (varInt) +
|
||||||
|
// max allowed useragent length + last block 4 bytes.
|
||||||
|
return 32 + (maxNetAddressPayload(pver) * 2) + maxVarIntPayload + MaxUserAgentLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVersion returns a new bitcoin version message that conforms to the
|
||||||
|
// Message interface using the passed parameters and defaults for the remaining
|
||||||
|
// fields.
|
||||||
|
func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64,
|
||||||
|
userAgent string, lastBlock int32) *MsgVersion {
|
||||||
|
|
||||||
|
// Limit the Timestamp to millisecond precision since the protocol
|
||||||
|
// doesn't support better.
|
||||||
|
return &MsgVersion{
|
||||||
|
ProtocolVersion: int32(ProtocolVersion),
|
||||||
|
Services: 0,
|
||||||
|
Timestamp: time.Unix(time.Now().Unix(), 0),
|
||||||
|
AddrYou: *you,
|
||||||
|
AddrMe: *me,
|
||||||
|
Nonce: nonce,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
LastBlock: lastBlock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgVersionFromConn is a convenience function that extracts the remote
|
||||||
|
// and local address from conn and returns a new bitcoin version message that
|
||||||
|
// conforms to the Message interface. See NewMsgVersion.
|
||||||
|
func NewMsgVersionFromConn(conn net.Conn, nonce uint64, userAgent string,
|
||||||
|
lastBlock int32) (*MsgVersion, error) {
|
||||||
|
|
||||||
|
// Don't assume any services until we know otherwise.
|
||||||
|
lna, err := NewNetAddress(conn.LocalAddr(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't assume any services until we know otherwise.
|
||||||
|
rna, err := NewNetAddress(conn.RemoteAddr(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewMsgVersion(lna, rna, nonce, userAgent, lastBlock), nil
|
||||||
|
}
|
277
msgversion_test.go
Normal file
277
msgversion_test.go
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestVersion tests the MsgVersion API.
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
|
||||||
|
// Create version message data.
|
||||||
|
userAgent := "/btcdtest:0.0.1/"
|
||||||
|
lastBlock := int32(234234)
|
||||||
|
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}
|
||||||
|
me, err := btcwire.NewNetAddress(tcpAddrMe, btcwire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
tcpAddrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}
|
||||||
|
you, err := btcwire.NewNetAddress(tcpAddrYou, btcwire.SFNodeNetwork)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
nonce, err := btcwire.RandomUint64()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("RandomUint64: error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the correct data back out.
|
||||||
|
msg := btcwire.NewMsgVersion(me, you, nonce, userAgent, lastBlock)
|
||||||
|
if msg.ProtocolVersion != int32(pver) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
||||||
|
msg.ProtocolVersion, pver)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg.AddrMe, me) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong me address - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.AddrMe), spew.Sdump(me))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg.AddrYou, you) {
|
||||||
|
t.Errorf("NewMsgVersion: wrong you address - got %v, want %v",
|
||||||
|
spew.Sdump(&msg.AddrYou), spew.Sdump(you))
|
||||||
|
}
|
||||||
|
if msg.Nonce != nonce {
|
||||||
|
t.Errorf("NewMsgVersion: wrong nonce - got %v, want %v",
|
||||||
|
msg.Nonce, nonce)
|
||||||
|
}
|
||||||
|
if msg.UserAgent != userAgent {
|
||||||
|
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
||||||
|
msg.UserAgent, userAgent)
|
||||||
|
}
|
||||||
|
if msg.LastBlock != lastBlock {
|
||||||
|
t.Errorf("NewMsgVersion: wrong last block - got %v, want %v",
|
||||||
|
msg.LastBlock, lastBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version message should not have any services set by default.
|
||||||
|
if msg.Services != 0 {
|
||||||
|
t.Errorf("NewMsgVersion: wrong default services - got %v, want %v",
|
||||||
|
msg.Services, 0)
|
||||||
|
|
||||||
|
}
|
||||||
|
if msg.HasService(btcwire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the command is expected value.
|
||||||
|
wantCmd := "version"
|
||||||
|
if cmd := msg.Command(); cmd != wantCmd {
|
||||||
|
t.Errorf("NewMsgVersion: wrong command - got %v want %v",
|
||||||
|
cmd, wantCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value.
|
||||||
|
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes +
|
||||||
|
// remote and local net addresses + nonce 8 bytes + length of user agent
|
||||||
|
// (varInt) + max allowed user agent length + last block 4 bytes.
|
||||||
|
wantPayload := uint32(2101)
|
||||||
|
maxPayload := msg.MaxPayloadLength(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("MaxPayloadLength: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding the full service node flag works.
|
||||||
|
msg.AddService(btcwire.SFNodeNetwork)
|
||||||
|
if msg.Services != btcwire.SFNodeNetwork {
|
||||||
|
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||||
|
msg.Services, btcwire.SFNodeNetwork)
|
||||||
|
}
|
||||||
|
if !msg.HasService(btcwire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection.
|
||||||
|
conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou}
|
||||||
|
msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the correct connection data back out.
|
||||||
|
if !msg.AddrMe.IP.Equal(tcpAddrMe.IP) {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: wrong me ip - got %v, want %v",
|
||||||
|
msg.AddrMe.IP, tcpAddrMe.IP)
|
||||||
|
}
|
||||||
|
if !msg.AddrYou.IP.Equal(tcpAddrYou.IP) {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: wrong you ip - got %v, want %v",
|
||||||
|
msg.AddrYou.IP, tcpAddrYou.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection with local UDP addresses to force a failure.
|
||||||
|
conn = &fakeConn{
|
||||||
|
localAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333},
|
||||||
|
remoteAddr: tcpAddrYou,
|
||||||
|
}
|
||||||
|
msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock)
|
||||||
|
if err != btcwire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: expected error not received "+
|
||||||
|
"- got %v, want %v", err, btcwire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fake connection with remote UDP addresses to force a failure.
|
||||||
|
conn = &fakeConn{
|
||||||
|
localAddr: tcpAddrMe,
|
||||||
|
remoteAddr: &net.UDPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333},
|
||||||
|
}
|
||||||
|
msg, err = btcwire.NewMsgVersionFromConn(conn, nonce, userAgent, lastBlock)
|
||||||
|
if err != btcwire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewMsgVersionFromConn: expected error not received "+
|
||||||
|
"- got %v, want %v", err, btcwire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAlertWire tests the MsgAlert wire encode and decode for various protocol
|
||||||
|
// versions.
|
||||||
|
func TestVersionWire(t *testing.T) {
|
||||||
|
// baseNetAddrYou is used in the various tests as a baseline remote
|
||||||
|
// NetAddress.
|
||||||
|
baseNetAddrYou := btcwire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("192.168.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNetAddrMe is used in the various tests as a baseline local
|
||||||
|
// NetAddress.
|
||||||
|
baseNetAddrMe := btcwire.NetAddress{
|
||||||
|
Timestamp: time.Time{}, // Zero value -- no timestamp in version
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseVersion is used in the various tests as a baseline version.
|
||||||
|
baseVersion := &btcwire.MsgVersion{
|
||||||
|
ProtocolVersion: 60002,
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST)
|
||||||
|
AddrYou: baseNetAddrYou,
|
||||||
|
AddrMe: baseNetAddrMe,
|
||||||
|
Nonce: 123123, // 0x1e0f3
|
||||||
|
UserAgent: "/btcdtest:0.0.1/",
|
||||||
|
LastBlock: 234234, // 0x392fa
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVersionEncoded := []byte{
|
||||||
|
0x62, 0xea, 0x00, 0x00, // Protocol version 60002
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // 64-bit Timestamp
|
||||||
|
// AddrYou -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01, // IP 192.168.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
// AddrMe -- No timestamp for NetAddress in version message
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Nonce
|
||||||
|
0x10, // Varint for user agent length
|
||||||
|
0x2f, 0x62, 0x74, 0x63, 0x64, 0x74, 0x65, 0x73,
|
||||||
|
0x74, 0x3a, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2f, // User agent
|
||||||
|
0xfa, 0x92, 0x03, 0x00, // Last block
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in *btcwire.MsgVersion // Message to encode
|
||||||
|
out *btcwire.MsgVersion // Expected decoded message
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0035Version.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
btcwire.BIP0035Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version BIP0031Version.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
btcwire.BIP0031Version,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version MultipleAddressVersion.
|
||||||
|
{
|
||||||
|
baseVersion,
|
||||||
|
baseVersion,
|
||||||
|
baseVersionEncoded,
|
||||||
|
btcwire.MultipleAddressVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode the message to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := test.in.BtcEncode(&buf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcEncode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("BtcEncode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var msg btcwire.MsgVersion
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = msg.BtcDecode(rbuf, test.pver)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BtcDecode #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&msg, test.out) {
|
||||||
|
t.Errorf("BtcDecode #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(msg), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
netaddress.go
Normal file
163
netaddress.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidNetAddr describes an error that indicates the caller didn't specify
|
||||||
|
// a TCP address as required.
|
||||||
|
var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr")
|
||||||
|
|
||||||
|
// maxNetAddressPayload returns the max payload size for a bitcoin NetAddress
|
||||||
|
// based on the protocol version.
|
||||||
|
func maxNetAddressPayload(pver uint32) uint32 {
|
||||||
|
// Services 8 bytes + ip 16 bytes + port 2 bytes.
|
||||||
|
plen := uint32(26)
|
||||||
|
|
||||||
|
// NetAddressTimeVersion added a timestamp field.
|
||||||
|
if pver >= NetAddressTimeVersion {
|
||||||
|
// Timestamp 4 bytes.
|
||||||
|
plen += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return plen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetAddress defines information about a peer on the network including the time
|
||||||
|
// it was last seen, the services it supports, its IP address, and port.
|
||||||
|
type NetAddress struct {
|
||||||
|
// Last time the address was seen. This is, unfortunately, encoded as a
|
||||||
|
// uint32 on the wire and therefore is limited to 2106. This field is
|
||||||
|
// not present in the bitcoin version message (MsgVersion) nor was it
|
||||||
|
// added until protocol version >= NetAddressTimeVersion.
|
||||||
|
Timestamp time.Time
|
||||||
|
|
||||||
|
// Bitfield which identifies the services supported by the address.
|
||||||
|
Services ServiceFlag
|
||||||
|
|
||||||
|
// IP address of the peer.
|
||||||
|
IP net.IP
|
||||||
|
|
||||||
|
// Port the peer is using. This is encoded in big endian on the wire
|
||||||
|
// which differs from most everything else.
|
||||||
|
Port uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasService returns whether the specified service is supported by the address.
|
||||||
|
func (na *NetAddress) HasService(service ServiceFlag) bool {
|
||||||
|
if na.Services&service == service {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddService adds service as a supported service by the peer generating the
|
||||||
|
// message.
|
||||||
|
func (na *NetAddress) AddService(service ServiceFlag) {
|
||||||
|
na.Services |= service
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddress is a convenience function to set the IP address and port in one
|
||||||
|
// call.
|
||||||
|
func (na *NetAddress) SetAddress(ip net.IP, port uint16) {
|
||||||
|
na.IP = ip
|
||||||
|
na.Port = port
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetAddress returns a new NetAddress using the provided TCP address and
|
||||||
|
// supported services with defaults for the remaining fields.
|
||||||
|
//
|
||||||
|
// Note that addr must be a net.TCPAddr. An ErrInvalidNetAddr is returned
|
||||||
|
// if it is not.
|
||||||
|
func NewNetAddress(addr net.Addr, services ServiceFlag) (*NetAddress, error) {
|
||||||
|
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrInvalidNetAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
na := NetAddress{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Services: services,
|
||||||
|
IP: tcpAddr.IP,
|
||||||
|
Port: uint16(tcpAddr.Port),
|
||||||
|
}
|
||||||
|
return &na, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readNetAddress reads an encoded NetAddress from r depending on the protocol
|
||||||
|
// version and whether or not the timestamp is included per ts. Some messages
|
||||||
|
// like version do not include the timestamp.
|
||||||
|
func readNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
var timestamp time.Time
|
||||||
|
var services ServiceFlag
|
||||||
|
var ip [16]byte
|
||||||
|
var port uint16
|
||||||
|
|
||||||
|
// NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will
|
||||||
|
// stop working somewhere around 2106. Also timestamp wasn't added until
|
||||||
|
// protocol version >= NetAddressTimeVersion
|
||||||
|
if ts && pver >= NetAddressTimeVersion {
|
||||||
|
var stamp uint32
|
||||||
|
err := readElement(r, &stamp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp = time.Unix(int64(stamp), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := readElements(r, &services, &ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Sigh. Bitcoin protocol mixes little and big endian.
|
||||||
|
err = binary.Read(r, binary.BigEndian, &port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
na.Timestamp = timestamp
|
||||||
|
na.Services = services
|
||||||
|
na.SetAddress(net.IP(ip[:]), port)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeNetAddress serializes a NetAddress to w depending on the protocol
|
||||||
|
// version and whether or not the timestamp is included per ts. Some messages
|
||||||
|
// like version do not include the timestamp.
|
||||||
|
func writeNetAddress(w io.Writer, pver uint32, na *NetAddress, ts bool) error {
|
||||||
|
// NOTE: The bitcoin protocol uses a uint32 for the timestamp so it will
|
||||||
|
// stop working somewhere around 2106. Also timestamp wasn't added until
|
||||||
|
// until protocol version >= NetAddressTimeVersion.
|
||||||
|
if ts && pver >= NetAddressTimeVersion {
|
||||||
|
err := writeElement(w, uint32(na.Timestamp.Unix()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure to always write 16 bytes even if the ip is nil.
|
||||||
|
var ip [16]byte
|
||||||
|
if na.IP != nil {
|
||||||
|
copy(ip[:], na.IP.To16())
|
||||||
|
}
|
||||||
|
err := writeElements(w, na.Services, ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigh. Bitcoin protocol mixes little and big endian.
|
||||||
|
err = binary.Write(w, binary.BigEndian, na.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
215
netaddress_test.go
Normal file
215
netaddress_test.go
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNetAddress tests the NetAddress API.
|
||||||
|
func TestNetAddress(t *testing.T) {
|
||||||
|
ip := net.ParseIP("127.0.0.1")
|
||||||
|
port := 8333
|
||||||
|
|
||||||
|
// Test NewNetAddress.
|
||||||
|
tcpAddr := &net.TCPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
na, err := btcwire.NewNetAddress(tcpAddr, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewNetAddress: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we get the same ip, port, and services back out.
|
||||||
|
if !na.IP.Equal(ip) {
|
||||||
|
t.Errorf("NetNetAddress: wrong ip - got %v, want %v", na.IP, ip)
|
||||||
|
}
|
||||||
|
if na.Port != uint16(port) {
|
||||||
|
t.Errorf("NetNetAddress: wrong port - got %v, want %v", na.Port,
|
||||||
|
port)
|
||||||
|
}
|
||||||
|
if na.Services != 0 {
|
||||||
|
t.Errorf("NetNetAddress: wrong services - got %v, want %v",
|
||||||
|
na.Services, 0)
|
||||||
|
}
|
||||||
|
if na.HasService(btcwire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure adding the full service node flag works.
|
||||||
|
na.AddService(btcwire.SFNodeNetwork)
|
||||||
|
if na.Services != btcwire.SFNodeNetwork {
|
||||||
|
t.Errorf("AddService: wrong services - got %v, want %v",
|
||||||
|
na.Services, btcwire.SFNodeNetwork)
|
||||||
|
}
|
||||||
|
if !na.HasService(btcwire.SFNodeNetwork) {
|
||||||
|
t.Errorf("HasService: SFNodeNetwork service not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure max payload is expected value for latest protocol version.
|
||||||
|
pver := btcwire.ProtocolVersion
|
||||||
|
wantPayload := uint32(30)
|
||||||
|
maxPayload := btcwire.TstMaxNetAddressPayload(btcwire.ProtocolVersion)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("maxNetAddressPayload: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol version before NetAddressTimeVersion when timestamp was
|
||||||
|
// added. Ensure max payload is expected value for it.
|
||||||
|
pver = btcwire.NetAddressTimeVersion - 1
|
||||||
|
wantPayload = 26
|
||||||
|
maxPayload = btcwire.TstMaxNetAddressPayload(pver)
|
||||||
|
if maxPayload != wantPayload {
|
||||||
|
t.Errorf("maxNetAddressPayload: wrong max payload length for "+
|
||||||
|
"protocol version %d - got %v, want %v", pver,
|
||||||
|
maxPayload, wantPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for expected failure on wrong address type.
|
||||||
|
udpAddr := &net.UDPAddr{}
|
||||||
|
_, err = btcwire.NewNetAddress(udpAddr, 0)
|
||||||
|
if err != btcwire.ErrInvalidNetAddr {
|
||||||
|
t.Errorf("NewNetAddress: expected error not received - "+
|
||||||
|
"got %v, want %v", err, btcwire.ErrInvalidNetAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNetAddressWire tests the NetAddress wire encode and decode for various
|
||||||
|
// protocol versions and timestamp flag combinations.
|
||||||
|
func TestNetAddressWire(t *testing.T) {
|
||||||
|
// baseNetAddr is used in the various tests as a baseline NetAddress.
|
||||||
|
baseNetAddr := btcwire.NetAddress{
|
||||||
|
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 12:15:05 -0600 CST
|
||||||
|
Services: btcwire.SFNodeNetwork,
|
||||||
|
IP: net.ParseIP("127.0.0.1"),
|
||||||
|
Port: 8333,
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNetAddrNoTS is baseNetAddr with a zero value for the timestamp.
|
||||||
|
baseNetAddrNoTS := baseNetAddr
|
||||||
|
baseNetAddrNoTS.Timestamp = time.Time{}
|
||||||
|
|
||||||
|
// baseNetAddrEncoded is the wire encoded bytes of baseNetAddr.
|
||||||
|
baseNetAddrEncoded := []byte{
|
||||||
|
0x29, 0xab, 0x5f, 0x49, // Timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNetAddrNoTSEncoded is the wire encoded bytes of baseNetAddrNoTS.
|
||||||
|
baseNetAddrNoTSEncoded := []byte{
|
||||||
|
// No timestamp
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SFNodeNetwork
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, // IP 127.0.0.1
|
||||||
|
0x20, 0x8d, // Port 8333 in big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.NetAddress // NetAddress to encode
|
||||||
|
out btcwire.NetAddress // Expected decoded NetAddress
|
||||||
|
ts bool // Include timestamp?
|
||||||
|
buf []byte // Wire encoding
|
||||||
|
pver uint32 // Protocol version for wire encoding
|
||||||
|
}{
|
||||||
|
// Latest protocol version without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Latest protocol version with ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddr,
|
||||||
|
true,
|
||||||
|
baseNetAddrEncoded,
|
||||||
|
btcwire.ProtocolVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion with ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddr,
|
||||||
|
true,
|
||||||
|
baseNetAddrEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion-1 without ts flag.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
false,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion - 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Protocol version NetAddressTimeVersion-1 with timestamp.
|
||||||
|
// Even though the timestamp flag is set, this shouldn't have a
|
||||||
|
// timestamp since it is a protocol version before it was
|
||||||
|
// added.
|
||||||
|
{
|
||||||
|
baseNetAddr,
|
||||||
|
baseNetAddrNoTS,
|
||||||
|
true,
|
||||||
|
baseNetAddrNoTSEncoded,
|
||||||
|
btcwire.NetAddressTimeVersion - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
// Encode to wire format.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := btcwire.TstWriteNetAddress(&buf, test.pver, &test.in, test.ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("writeNetAddress #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||||
|
t.Errorf("writeNetAddress #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the message from wire format.
|
||||||
|
var na btcwire.NetAddress
|
||||||
|
rbuf := bytes.NewBuffer(test.buf)
|
||||||
|
err = btcwire.TstReadNetAddress(rbuf, test.pver, &na, test.ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("readNetAddress #%d error %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(na, test.out) {
|
||||||
|
t.Errorf("readNetAddress #%d\n got: %s want: %s", i,
|
||||||
|
spew.Sdump(na), spew.Sdump(test.out))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
protocol.go
Normal file
83
protocol.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MainPort = "8333"
|
||||||
|
TestNetPort = "18333"
|
||||||
|
ProtocolVersion uint32 = 60002
|
||||||
|
TxVersion = 1
|
||||||
|
|
||||||
|
// MultipleAddressVersion is the protocol version which added multiple
|
||||||
|
// addresses per message (pver >= MultipleAddressVersion).
|
||||||
|
MultipleAddressVersion uint32 = 209
|
||||||
|
|
||||||
|
// NetAddressTimeVersion is the protocol version which added the
|
||||||
|
// timestamp field (pver >= NetAddressTimeVersion).
|
||||||
|
NetAddressTimeVersion uint32 = 31402
|
||||||
|
|
||||||
|
// BIP0031Version is the protocol version AFTER which a pong message
|
||||||
|
// and nonce field in ping were added (pver > BIP0031Version).
|
||||||
|
BIP0031Version uint32 = 60000
|
||||||
|
|
||||||
|
// BIP0035Version is the protocol version which added the mempool
|
||||||
|
// message (pver >= BIP0035Version).
|
||||||
|
BIP0035Version uint32 = 60002
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceFlag identifies services supported by a bitcoin peer.
|
||||||
|
type ServiceFlag uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
SFNodeNetwork ServiceFlag = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of service flags back to their constant names for pretty printing.
|
||||||
|
var sfStrings = map[ServiceFlag]string{
|
||||||
|
SFNodeNetwork: "SFNodeNetwork",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the ServiceFlag in human-readable form.
|
||||||
|
func (f ServiceFlag) String() string {
|
||||||
|
// No flags are set.
|
||||||
|
if f == 0 {
|
||||||
|
return "0x0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add individual bit flags.
|
||||||
|
s := ""
|
||||||
|
for flag, name := range sfStrings {
|
||||||
|
if f&flag == flag {
|
||||||
|
s += name + "|"
|
||||||
|
f -= flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining flags which aren't accounted for as hex.
|
||||||
|
s = strings.TrimRight(s, "|")
|
||||||
|
if f != 0 {
|
||||||
|
s += "|0x" + strconv.FormatUint(uint64(f), 16)
|
||||||
|
}
|
||||||
|
s = strings.TrimLeft(s, "|")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitcoinNet represents which bitcoin network a message belongs to.
|
||||||
|
type BitcoinNet uint32
|
||||||
|
|
||||||
|
// Constants used to indicate the message bitcoin network. They can also be
|
||||||
|
// used to seek to the next message when a stream's state is unknown, but
|
||||||
|
// this package does not provide that functionality since it's generally a
|
||||||
|
// better idea to simply disconnect clients that are misbehaving over TCP.
|
||||||
|
const (
|
||||||
|
MainNet BitcoinNet = 0xd9b4bef9
|
||||||
|
TestNet BitcoinNet = 0xdab5bffa
|
||||||
|
TestNet3 BitcoinNet = 0x0709110b
|
||||||
|
)
|
32
protocol_test.go
Normal file
32
protocol_test.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestServiceFlagStringer tests the stringized output for service flag types.
|
||||||
|
func TestServiceFlagStringer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in btcwire.ServiceFlag
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0x0"},
|
||||||
|
{btcwire.SFNodeNetwork, "SFNodeNetwork"},
|
||||||
|
{0xffffffff, "SFNodeNetwork|0xfffffffe"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result := test.in.String()
|
||||||
|
if result != test.want {
|
||||||
|
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||||
|
test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
106
shahash.go
Normal file
106
shahash.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Size of array used to store sha hashes. See ShaHash.
|
||||||
|
const HashSize = 32
|
||||||
|
const MaxHashStringSize = HashSize * 2
|
||||||
|
|
||||||
|
var ErrHashStrSize = fmt.Errorf("Max hash length is %v chars", MaxHashStringSize)
|
||||||
|
|
||||||
|
// ShaHash is used in several of the bitcoin messages and common structures. It
|
||||||
|
// typically represents the double sha256 of data.
|
||||||
|
type ShaHash [HashSize]byte
|
||||||
|
|
||||||
|
// String returns the ShaHash in the standard bitcoin big-endian form.
|
||||||
|
func (hash *ShaHash) String() string {
|
||||||
|
hashstr := ""
|
||||||
|
for i := range hash {
|
||||||
|
hashstr += fmt.Sprintf("%02x", hash[HashSize-1-i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashstr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes which represent the hash as a byte slice.
|
||||||
|
func (hash *ShaHash) Bytes() []byte {
|
||||||
|
newHash := make([]byte, HashSize)
|
||||||
|
copy(newHash, hash[:])
|
||||||
|
|
||||||
|
return newHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes sets the bytes which represent the hash. An error is returned if
|
||||||
|
// the number of bytes passed in is not HashSize.
|
||||||
|
func (hash *ShaHash) SetBytes(newHash []byte) error {
|
||||||
|
nhlen := len(newHash)
|
||||||
|
if nhlen != HashSize {
|
||||||
|
return fmt.Errorf("ShaHash: invalid sha length of %v, want %v",
|
||||||
|
nhlen, HashSize)
|
||||||
|
}
|
||||||
|
copy(hash[:], newHash[0:HashSize])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual returns true if target is the same as hash.
|
||||||
|
func (hash *ShaHash) IsEqual(target *ShaHash) bool {
|
||||||
|
return bytes.Equal(hash[:], target[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShaHash returns a new ShaHash from a byte slice. An error is returned if
|
||||||
|
// the number of bytes passed in is not HashSize.
|
||||||
|
func NewShaHash(newHash []byte) (*ShaHash, error) {
|
||||||
|
var sh ShaHash
|
||||||
|
err := sh.SetBytes(newHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sh, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShaHashFromStr converts a hash string in the standard bitcoin big-endian
|
||||||
|
// form to a ShaHash (which is little-endian).
|
||||||
|
func NewShaHashFromStr(hash string) (*ShaHash, error) {
|
||||||
|
// Return error is hash string is too long.
|
||||||
|
if len(hash) > MaxHashStringSize {
|
||||||
|
return nil, ErrHashStrSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex decoder expects the hash to be a multiple of two.
|
||||||
|
if len(hash)%2 != 0 {
|
||||||
|
hash = "0" + hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string hash to bytes.
|
||||||
|
buf, err := hex.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The string was given in big-endian, so reverse the bytes to little
|
||||||
|
// endian.
|
||||||
|
blen := len(buf)
|
||||||
|
for i := 0; i < blen/2; i++ {
|
||||||
|
buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the byte slice is the right length by appending zeros to
|
||||||
|
// pad it out.
|
||||||
|
pbuf := buf
|
||||||
|
if HashSize-blen > 0 {
|
||||||
|
pbuf = make([]byte, HashSize)
|
||||||
|
copy(pbuf, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the sha hash using the byte slice and return it.
|
||||||
|
return NewShaHash(pbuf)
|
||||||
|
}
|
167
shahash_test.go
Normal file
167
shahash_test.go
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcwire_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestShaHash tests the ShaHash API.
|
||||||
|
func TestShaHash(t *testing.T) {
|
||||||
|
|
||||||
|
// Hash of block 234439.
|
||||||
|
blockHashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef"
|
||||||
|
blockHash, err := btcwire.NewShaHashFromStr(blockHashStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHashFromStr: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash of block 234440 as byte slice.
|
||||||
|
buf := []byte{
|
||||||
|
0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1,
|
||||||
|
0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8,
|
||||||
|
0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f,
|
||||||
|
0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := btcwire.NewShaHash(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewShaHash: unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure proper size.
|
||||||
|
if len(hash) != btcwire.HashSize {
|
||||||
|
t.Errorf("NewShaHash: hash length mismatch - got: %v, want: %v",
|
||||||
|
len(hash), btcwire.HashSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure contents match.
|
||||||
|
if !bytes.Equal(hash[:], buf) {
|
||||||
|
t.Errorf("NewShaHash: hash contents mismatch - got: %v, want: %v",
|
||||||
|
hash[:], buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure contents of hash of block 234440 don't match 234439.
|
||||||
|
if hash.IsEqual(blockHash) {
|
||||||
|
t.Errorf("IsEqual: hash contents should not match - got: %v, want: %v",
|
||||||
|
hash, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set hash from byte slice and ensure contents match.
|
||||||
|
err = hash.SetBytes(blockHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SetBytes: %v", err)
|
||||||
|
}
|
||||||
|
if !hash.IsEqual(blockHash) {
|
||||||
|
t.Errorf("IsEqual: hash contents mismatch - got: %v, want: %v",
|
||||||
|
hash, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid size for SetBytes.
|
||||||
|
err = hash.SetBytes([]byte{0x00})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("SetBytes: failed to received expected err - got: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestShaHashString tests the stringized output for sha hashes.
|
||||||
|
func TestShaHashString(t *testing.T) {
|
||||||
|
// Block 100000 hash.
|
||||||
|
wantStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||||
|
hash := &btcwire.ShaHash{
|
||||||
|
0x06, 0xe5, 0x33, 0xfd, 0x1a, 0xda, 0x86, 0x39,
|
||||||
|
0x1f, 0x3f, 0x6c, 0x34, 0x32, 0x04, 0xb0, 0xd2,
|
||||||
|
0x78, 0xd4, 0xaa, 0xec, 0x1c, 0x0b, 0x20, 0xaa,
|
||||||
|
0x27, 0xba, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
hashStr := hash.String()
|
||||||
|
if hashStr != wantStr {
|
||||||
|
t.Errorf("String: wrong hash string - got %v, want %v",
|
||||||
|
hashStr, wantStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewShaHashFromStr executes tests against the NewShaHashFromStr function.
|
||||||
|
func TestNewShaHashFromStr(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
want btcwire.ShaHash
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
// Genesis hash.
|
||||||
|
{
|
||||||
|
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||||
|
btcwire.GenesisHash,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Genesis hash with stripped leading zeros.
|
||||||
|
{
|
||||||
|
"19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
||||||
|
btcwire.GenesisHash,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Single digit hash.
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
btcwire.ShaHash{
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Block 203707 with stripped leading zeros.
|
||||||
|
{
|
||||||
|
"3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc",
|
||||||
|
btcwire.ShaHash{
|
||||||
|
0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7,
|
||||||
|
0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b,
|
||||||
|
0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b,
|
||||||
|
0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hash string that is too long.
|
||||||
|
{
|
||||||
|
"01234567890123456789012345678901234567890123456789012345678912345",
|
||||||
|
btcwire.ShaHash{},
|
||||||
|
btcwire.ErrHashStrSize,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hash string that is contains non-hex chars.
|
||||||
|
{
|
||||||
|
"abcdefg",
|
||||||
|
btcwire.ShaHash{},
|
||||||
|
hex.InvalidByteError('g'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unexpectedErrStr := "NewShaHashFromStr #%d failed to detect expected error - got: %v want: %v"
|
||||||
|
unexpectedResultStr := "NewShaHashFromStr #%d got: %v want: %v"
|
||||||
|
t.Logf("Running %d tests", len(tests))
|
||||||
|
for i, test := range tests {
|
||||||
|
result, err := btcwire.NewShaHashFromStr(test.in)
|
||||||
|
if err != test.err {
|
||||||
|
t.Errorf(unexpectedErrStr, i, err, test.err)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
// Got expected error. Move on to the next test.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !test.want.IsEqual(result) {
|
||||||
|
t.Errorf(unexpectedResultStr, i, result, &test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
152
test_coverage.txt
Normal file
152
test_coverage.txt
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.Copy 100.00% (24/24)
|
||||||
|
github.com/conformal/btcwire/common.go readVarInt 100.00% (24/24)
|
||||||
|
github.com/conformal/btcwire/common.go writeVarInt 100.00% (16/16)
|
||||||
|
github.com/conformal/btcwire/shahash.go NewShaHashFromStr 100.00% (15/15)
|
||||||
|
github.com/conformal/btcwire/protocol.go ServiceFlag.String 100.00% (12/12)
|
||||||
|
github.com/conformal/btcwire/common.go readVarString 100.00% (8/8)
|
||||||
|
github.com/conformal/btcwire/common.go writeVarString 100.00% (7/7)
|
||||||
|
github.com/conformal/btcwire/common.go DoubleSha256 100.00% (7/7)
|
||||||
|
github.com/conformal/btcwire/common.go randomUint64 100.00% (7/7)
|
||||||
|
github.com/conformal/btcwire/msgversion.go NewMsgVersionFromConn 100.00% (7/7)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go MsgHeaders.AddBlockHeader 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.AddAddress 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.AddAddresses 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go MsgGetData.AddInvVect 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go MsgNotFound.AddInvVect 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/common.go writeElements 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/shahash.go ShaHash.SetBytes 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msginv.go MsgInv.AddInvVect 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.AddBlockLocatorHash 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.AddBlockLocatorHash 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/netaddress.go NewNetAddress 100.00% (5/5)
|
||||||
|
github.com/conformal/btcwire/msgping.go MsgPing.MaxPayloadLength 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/msgpong.go MsgPong.MaxPayloadLength 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/msgmempool.go MsgMemPool.BtcEncode 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/shahash.go ShaHash.String 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/netaddress.go maxNetAddressPayload 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/msgmempool.go MsgMemPool.BtcDecode 100.00% (4/4)
|
||||||
|
github.com/conformal/btcwire/shahash.go ShaHash.Bytes 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/invvect.go InvType.String 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.HasService 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.AddTransaction 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.MaxPayloadLength 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/netaddress.go NetAddress.HasService 100.00% (3/3)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.ClearTransactions 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwire/netaddress.go NetAddress.SetAddress 100.00% (2/2)
|
||||||
|
github.com/conformal/btcwire/msgverack.go MsgVerAck.BtcEncode 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgverack.go MsgVerAck.BtcDecode 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/common.go readElement 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/common.go writeElement 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/common.go RandomUint64 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/invvect.go NewInvVect 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgalert.go MsgAlert.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/shahash.go ShaHash.IsEqual 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.ClearAddresses 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go NewMsgAddr 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/blockheader.go NewBlockHeader 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgalert.go MsgAlert.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgalert.go NewMsgAlert 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.BlockSha 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgblock.go NewMsgBlock 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.BtcDecode 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.BtcEncode 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetaddr.go MsgGetAddr.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetaddr.go NewMsgGetAddr 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/netaddress.go NetAddress.AddService 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go NewMsgGetBlocks 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go MsgGetData.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go MsgGetData.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go NewMsgGetData 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgverack.go MsgVerAck.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go MsgNotFound.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go MsgNotFound.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go NewMsgNotFound 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgping.go MsgPing.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgping.go NewMsgPing 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgpong.go MsgPong.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgpong.go NewMsgPong 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go NewOutPoint 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go NewTxIn 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go NewTxOut 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.AddTxIn 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.AddTxOut 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgverack.go MsgVerAck.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgtx.go NewMsgTx 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgversion.go NewMsgVersion 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go NewMsgGetHeaders 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go MsgHeaders.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go MsgHeaders.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go NewMsgHeaders 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.AddService 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msginv.go MsgInv.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msginv.go MsgInv.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msginv.go NewMsgInv 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgmempool.go MsgMemPool.Command 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgmempool.go MsgMemPool.MaxPayloadLength 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgmempool.go NewMsgMemPool 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/msgverack.go NewMsgVerAck 100.00% (1/1)
|
||||||
|
github.com/conformal/btcwire/message.go makeEmptyMessage 94.44% (17/18)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.TxShas 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwire/msgpong.go MsgPong.BtcEncode 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwire/msgpong.go MsgPong.BtcDecode 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.TxSha 85.71% (6/7)
|
||||||
|
github.com/conformal/btcwire/netaddress.go readNetAddress 85.00% (17/20)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecode 80.00% (8/10)
|
||||||
|
github.com/conformal/btcwire/blockheader.go readBlockHeader 80.00% (8/10)
|
||||||
|
github.com/conformal/btcwire/shahash.go NewShaHash 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwire/msgping.go MsgPing.BtcDecode 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwire/msgping.go MsgPing.BtcEncode 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwire/common.go readElements 80.00% (4/5)
|
||||||
|
github.com/conformal/btcwire/netaddress.go writeNetAddress 78.57% (11/14)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.BtcEncode 77.78% (7/9)
|
||||||
|
github.com/conformal/btcwire/msgtx.go readTxIn 76.47% (13/17)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.BtcDecode 76.00% (19/25)
|
||||||
|
github.com/conformal/btcwire/msgtx.go readTxOut 75.00% (9/12)
|
||||||
|
github.com/conformal/btcwire/blockheader.go BlockHeader.BlockSha 75.00% (6/8)
|
||||||
|
github.com/conformal/btcwire/blockheader.go writeBlockHeader 75.00% (6/8)
|
||||||
|
github.com/conformal/btcwire/msgalert.go MsgAlert.BtcDecode 75.00% (6/8)
|
||||||
|
github.com/conformal/btcwire/msgalert.go MsgAlert.BtcEncode 75.00% (6/8)
|
||||||
|
github.com/conformal/btcwire/invvect.go readInvVect 75.00% (3/4)
|
||||||
|
github.com/conformal/btcwire/msgtx.go writeOutPoint 75.00% (3/4)
|
||||||
|
github.com/conformal/btcwire/msgtx.go readOutPoint 75.00% (3/4)
|
||||||
|
github.com/conformal/btcwire/invvect.go writeInvVect 75.00% (3/4)
|
||||||
|
github.com/conformal/btcwire/msgtx.go MsgTx.BtcEncode 73.91% (17/23)
|
||||||
|
github.com/conformal/btcwire/msgtx.go writeTxIn 73.33% (11/15)
|
||||||
|
github.com/conformal/btcwire/msgtx.go writeTxOut 72.73% (8/11)
|
||||||
|
github.com/conformal/btcwire/message.go WriteMessage 70.00% (21/30)
|
||||||
|
github.com/conformal/btcwire/message.go readMessageHeader 70.00% (7/10)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcDecode 69.23% (9/13)
|
||||||
|
github.com/conformal/btcwire/msginv.go MsgInv.BtcDecode 69.23% (9/13)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcDecode 69.23% (9/13)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcDecode 69.23% (9/13)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcDecode 68.42% (13/19)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcDecode 68.42% (13/19)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.BtcDecode 68.00% (17/25)
|
||||||
|
github.com/conformal/btcwire/msggetblocks.go MsgGetBlocks.BtcEncode 66.67% (12/18)
|
||||||
|
github.com/conformal/btcwire/msggetheaders.go MsgGetHeaders.BtcEncode 66.67% (12/18)
|
||||||
|
github.com/conformal/btcwire/msggetdata.go MsgGetData.BtcEncode 66.67% (8/12)
|
||||||
|
github.com/conformal/btcwire/msgnotfound.go MsgNotFound.BtcEncode 66.67% (8/12)
|
||||||
|
github.com/conformal/btcwire/msginv.go MsgInv.BtcEncode 66.67% (8/12)
|
||||||
|
github.com/conformal/btcwire/msgversion.go MsgVersion.BtcEncode 63.64% (14/22)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcDecode 62.50% (10/16)
|
||||||
|
github.com/conformal/btcwire/msgaddr.go MsgAddr.BtcEncode 60.00% (9/15)
|
||||||
|
github.com/conformal/btcwire/msgheaders.go MsgHeaders.BtcEncode 60.00% (9/15)
|
||||||
|
github.com/conformal/btcwire/message.go ReadMessage 57.14% (4/7)
|
||||||
|
github.com/conformal/btcwire/message.go readMessage 53.12% (17/32)
|
||||||
|
github.com/conformal/btcwire/msgblock.go MsgBlock.BtcDecodeTxLoc 0.00% (0/16)
|
||||||
|
github.com/conformal/btcwire/message.go discardInput 0.00% (0/10)
|
||||||
|
github.com/conformal/btcwire --------------------------------- 78.43% (731/932)
|
||||||
|
|
Loading…
Reference in a new issue