Initial implementation of BIP0037.

Implement filteradd, filterclear, filterload, and merkleblock.
This commit is contained in:
David Hill 2014-04-10 22:29:00 -04:00 committed by Dave Collins
parent a98f5ca38e
commit cf754d09bf
11 changed files with 1108 additions and 22 deletions

View file

@ -26,6 +26,7 @@ const (
InvTypeError InvType = 0 InvTypeError InvType = 0
InvTypeTx InvType = 1 InvTypeTx InvType = 1
InvTypeBlock InvType = 2 InvTypeBlock InvType = 2
InvTypeFilteredBlock InvType = 3
) )
// Map of service flags back to their constant names for pretty printing. // Map of service flags back to their constant names for pretty printing.
@ -33,6 +34,7 @@ var ivStrings = map[InvType]string{
InvTypeError: "ERROR", InvTypeError: "ERROR",
InvTypeTx: "MSG_TX", InvTypeTx: "MSG_TX",
InvTypeBlock: "MSG_BLOCK", InvTypeBlock: "MSG_BLOCK",
InvTypeFilteredBlock: "MSG_FILTERED_BLOCK",
} }
// String returns the InvType in human-readable form. // String returns the InvType in human-readable form.

View file

@ -42,6 +42,10 @@ const (
cmdPong = "pong" cmdPong = "pong"
cmdAlert = "alert" cmdAlert = "alert"
cmdMemPool = "mempool" cmdMemPool = "mempool"
cmdFilterAdd = "filteradd"
cmdFilterClear = "filterclear"
cmdFilterLoad = "filterload"
cmdMerkleBlock = "merkleblock"
) )
// Message is an interface that describes a bitcoin message. A type that // Message is an interface that describes a bitcoin message. A type that
@ -108,6 +112,18 @@ func makeEmptyMessage(command string) (Message, error) {
case cmdMemPool: case cmdMemPool:
msg = &MsgMemPool{} msg = &MsgMemPool{}
case cmdFilterAdd:
msg = &MsgFilterAdd{}
case cmdFilterClear:
msg = &MsgFilterClear{}
case cmdFilterLoad:
msg = &MsgFilterLoad{}
case cmdMerkleBlock:
msg = &MsgMerkleBlock{}
default: default:
return nil, fmt.Errorf("unhandled command [%s]", command) return nil, fmt.Errorf("unhandled command [%s]", command)
} }

View file

@ -68,6 +68,13 @@ func TestMessage(t *testing.T) {
msgHeaders := btcwire.NewMsgHeaders() msgHeaders := btcwire.NewMsgHeaders()
msgAlert := btcwire.NewMsgAlert([]byte("payload"), []byte("signature")) msgAlert := btcwire.NewMsgAlert([]byte("payload"), []byte("signature"))
msgMemPool := btcwire.NewMsgMemPool() msgMemPool := btcwire.NewMsgMemPool()
msgFilterAdd := btcwire.NewMsgFilterAdd([]byte{0x01})
msgFilterClear := btcwire.NewMsgFilterClear()
msgFilterLoad := btcwire.NewMsgFilterLoad([]byte{0x01}, 10, 0, btcwire.BloomUpdateNone)
//
bh := btcwire.NewBlockHeader(&btcwire.ShaHash{}, &btcwire.ShaHash{}, 0, 0)
msgMerkleBlock := btcwire.NewMsgMerkleBlock(bh)
tests := []struct { tests := []struct {
in btcwire.Message // Value to encode in btcwire.Message // Value to encode
@ -92,6 +99,10 @@ func TestMessage(t *testing.T) {
{msgHeaders, msgHeaders, pver, btcwire.MainNet, 25}, {msgHeaders, msgHeaders, pver, btcwire.MainNet, 25},
{msgAlert, msgAlert, pver, btcwire.MainNet, 42}, {msgAlert, msgAlert, pver, btcwire.MainNet, 42},
{msgMemPool, msgMemPool, pver, btcwire.MainNet, 24}, {msgMemPool, msgMemPool, pver, btcwire.MainNet, 24},
{msgFilterAdd, msgFilterAdd, pver, btcwire.MainNet, 26},
{msgFilterClear, msgFilterClear, pver, btcwire.MainNet, 24},
{msgFilterLoad, msgFilterLoad, pver, btcwire.MainNet, 35},
{msgMerkleBlock, msgMerkleBlock, pver, btcwire.MainNet, 110},
} }
t.Logf("Running %d tests", len(tests)) t.Logf("Running %d tests", len(tests))

103
msgfilteradd.go Normal file
View file

@ -0,0 +1,103 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire
import (
"fmt"
"io"
)
const (
// MaxFilterAddDataSize is the maximum byte size of a data
// element to add to the Bloom filter. It is equal to the
// maximum element size of a script.
MaxFilterAddDataSize = 520
)
// MsgFilterAdd implements the Message interface and represents a bitcoin filteradd
// message which is used to add a data element to an existing Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgFilterAdd struct {
Data []byte
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgFilterAdd) BtcDecode(r io.Reader, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filteradd message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterAdd.BtcDecode", str)
}
size, err := readVarInt(r, pver)
if err != nil {
return err
}
if size > MaxFilterAddDataSize {
str := fmt.Sprintf("filteradd size too large for message "+
"[size %v, max %v]", size, MaxFilterAddDataSize)
return messageError("MsgFilterAdd.BtcDecode", str)
}
msg.Data = make([]byte, size)
_, err = io.ReadFull(r, msg.Data)
if err != nil {
return err
}
return nil
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgFilterAdd) BtcEncode(w io.Writer, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filteradd message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterAdd.BtcEncode", str)
}
size := len(msg.Data)
if size > MaxFilterAddDataSize {
str := fmt.Sprintf("filteradd size too large for message "+
"[size %v, max %v]", size, MaxFilterAddDataSize)
return messageError("MsgFilterAdd.BtcEncode", str)
}
err := writeVarInt(w, pver, uint64(size))
if err != nil {
return err
}
err = writeElement(w, msg.Data)
if err != nil {
return err
}
return nil
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgFilterAdd) Command() string {
return cmdFilterAdd
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgFilterAdd) MaxPayloadLength(pver uint32) uint32 {
return MaxVarIntPayload + MaxFilterAddDataSize
}
// NewMsgFilterAdd returns a new bitcoin filteradd message that conforms to the Message
// interface. See MsgFilterAdd for details.
func NewMsgFilterAdd(data []byte) *MsgFilterAdd {
return &MsgFilterAdd{
Data: data,
}
}

169
msgfilteradd_test.go Normal file
View file

@ -0,0 +1,169 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire_test
import (
"bytes"
"github.com/conformal/btcwire"
"io"
"testing"
)
// TestFilterCLearLatest tests the MsgFilterAdd API against the latest protocol version.
func TestFilterAddLatest(t *testing.T) {
pver := btcwire.ProtocolVersion
data := []byte{0x01, 0x02}
msg := btcwire.NewMsgFilterAdd(data)
// Ensure the command is expected value.
wantCmd := "filteradd"
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgFilterAdd: wrong command - got %v want %v",
cmd, wantCmd)
}
// Ensure max payload is expected value for latest protocol version.
wantPayload := uint32(529)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+
"protocol version %d - got %v, want %v", pver,
maxPayload, wantPayload)
}
// Test encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, pver)
if err != nil {
t.Errorf("encode of MsgFilterAdd failed %v err <%v>", msg, err)
}
// Test decode with latest protocol version.
readmsg := btcwire.MsgFilterAdd{}
err = readmsg.BtcDecode(&buf, pver)
if err != nil {
t.Errorf("decode of MsgFilterAdd failed [%v] err <%v>", buf, err)
}
return
}
// TestFilterAddCrossProtocol tests the MsgFilterAdd API when encoding with the latest
// protocol version and decoded with BIP0031Version.
func TestFilterAddCrossProtocol(t *testing.T) {
data := []byte{0x01, 0x02}
msg := btcwire.NewMsgFilterAdd(data)
// Encode with old protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.BIP0037Version-1)
if err == nil {
t.Errorf("encode of MsgFilterAdd succeeded when it shouldn't have %v",
msg)
}
// Decode with old protocol version.
readmsg := btcwire.MsgFilterAdd{}
err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version)
if err == nil {
t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't have %v",
msg)
}
}
// TestFilterAddMaxDataSize tests the MsgFilterAdd API maximum data size.
func TestFilterAddMaxDataSize(t *testing.T) {
data := bytes.Repeat([]byte{0xff}, 521)
msg := btcwire.NewMsgFilterAdd(data)
// Encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("encode of MsgFilterAdd succeeded when it shouldn't have %v",
msg)
}
// Decode with latest protocol version.
readbuf := bytes.NewReader(data)
err = msg.BtcDecode(readbuf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("decode of MsgFilterAdd succeeded when it shouldn't have %v",
msg)
}
}
// TestFilterAddWireErrors performs negative tests against wire encode and decode
// of MsgFilterAdd to confirm error paths work correctly.
func TestFilterAddWireErrors(t *testing.T) {
pver := btcwire.ProtocolVersion
tests := []struct {
in *btcwire.MsgFilterAdd // Value to encode
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
max int // Max size of fixed buffer to induce errors
writeErr error // Expected write error
readErr error // Expected read error
}{
// Latest protocol version with intentional read/write errors.
{
&btcwire.MsgFilterAdd{Data: []byte{0x01, 0x02, 0x03, 0x04}},
[]byte{
0x05, // Varint for size of data
0x02, 0x03, 0x04, // Data
},
pver,
2,
io.ErrShortWrite,
io.ErrUnexpectedEOF,
},
{
&btcwire.MsgFilterAdd{Data: []byte{0x01, 0x02, 0x03, 0x04}},
[]byte{
0x05, // Varint for size of data
0x02, 0x03, 0x04, // Data
},
pver,
0,
io.ErrShortWrite,
io.EOF,
},
{
&btcwire.MsgFilterAdd{Data: []byte{0x01, 0x02, 0x03, 0x04}},
[]byte{
0x05, // Varint for size of data
0x02, 0x03, 0x04, // Data
},
pver,
1,
io.ErrShortWrite,
io.EOF,
},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Encode to wire format.
w := newFixedWriter(test.max)
err := test.in.BtcEncode(w, test.pver)
if err != test.writeErr {
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
i, err, test.writeErr)
continue
}
// Decode from wire format.
var msg btcwire.MsgFilterAdd
r := newFixedReader(test.max, test.buf)
err = msg.BtcDecode(r, test.pver)
if err != test.readErr {
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
i, err, test.readErr)
continue
}
}
}

58
msgfilterclear.go Normal file
View file

@ -0,0 +1,58 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire
import (
"fmt"
"io"
)
// MsgFilterClear implements the Message interface and represents a bitcoin filterclear
// message which is used to reset a Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgFilterClear struct{}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgFilterClear) BtcDecode(r io.Reader, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filterclear message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterClear.BtcDecode", str)
}
return nil
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgFilterClear) BtcEncode(w io.Writer, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filterclear message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterClear.BtcEncode", str)
}
return nil
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgFilterClear) Command() string {
return cmdFilterClear
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgFilterClear) MaxPayloadLength(pver uint32) uint32 {
return 0
}
// NewMsgFilterClear returns a new bitcoin filterclear message that conforms to the Message
// interface. See MsgFilterClear for details.
func NewMsgFilterClear() *MsgFilterClear {
return &MsgFilterClear{}
}

72
msgfilterclear_test.go Normal file
View file

@ -0,0 +1,72 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire_test
import (
"bytes"
"github.com/conformal/btcwire"
"testing"
)
// TestFilterCLearLatest tests the MsgFilterClear API against the latest protocol version.
func TestFilterClearLatest(t *testing.T) {
pver := btcwire.ProtocolVersion
msg := btcwire.NewMsgFilterClear()
// Ensure the command is expected value.
wantCmd := "filterclear"
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgFilterClear: wrong command - got %v want %v",
cmd, wantCmd)
}
// Ensure max payload is expected value for latest protocol version.
wantPayload := uint32(0)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+
"protocol version %d - got %v, want %v", pver,
maxPayload, wantPayload)
}
// Test encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, pver)
if err != nil {
t.Errorf("encode of MsgFilterClear failed %v err <%v>", msg, err)
}
// Test decode with latest protocol version.
readmsg := btcwire.NewMsgFilterClear()
err = readmsg.BtcDecode(&buf, pver)
if err != nil {
t.Errorf("decode of MsgFilterClear failed [%v] err <%v>", buf, err)
}
return
}
// TestFilterClearCrossProtocol tests the MsgFilterClear API when encoding with the latest
// protocol version and decoded with BIP0031Version.
func TestFilterClearCrossProtocol(t *testing.T) {
msg := btcwire.NewMsgFilterClear()
// Encode with old protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.BIP0037Version-1)
if err == nil {
t.Errorf("encode of MsgFilterClear succeeded when it shouldn't have %v",
msg)
}
// Decode with old protocol version.
readmsg := btcwire.NewMsgFilterClear()
err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version)
if err == nil {
t.Errorf("decode of MsgFilterClear succeeded when it shouldn't have %v",
msg)
}
}

160
msgfilterload.go Normal file
View file

@ -0,0 +1,160 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire
import (
"fmt"
"io"
)
type BloomUpdateType uint8
const (
// BloomUpdateNone indicates the filter is not adjusted when a match is found.
BloomUpdateNone BloomUpdateType = 0
// BloomUpdateAll indicates if the filter matches any data element in a
// scriptPubKey, the outpoint is serialized and inserted into the filter.
BloomUpdateAll BloomUpdateType = 1
// BloomUpdateP2PubkeyOnly indicates if the filter matches a data element in
// a scriptPubkey and the script is of the standard payToPybKey or payToMultiSig,
// the outpoint is inserted into the filter.
BloomUpdateP2PubkeyOnly BloomUpdateType = 2
)
const (
// MaxFilterLoadHashFuncs is the maximum number of hash functions to load
// into the Bloom filter.
MaxFilterLoadHashFuncs = 50
// MaxFilterLoadFilterSize is the maximum size in bytes a filter may be.
MaxFilterLoadFilterSize = 36000
)
// MsgFilterLoad implements the Message interface and represents a bitcoin filterload
// message which is used to reset a Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgFilterLoad struct {
Filter []byte
HashFuncs uint32
Tweak uint32
Flags BloomUpdateType
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgFilterLoad) BtcDecode(r io.Reader, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filterload message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterLoad.BtcDecode", str)
}
// Read num filter and limit to max.
size, err := readVarInt(r, pver)
if err != nil {
return err
}
if size > MaxFilterLoadFilterSize {
str := fmt.Sprintf("filterload filter size too large for message "+
"[size %v, max %v]", size, MaxFilterLoadFilterSize)
return messageError("MsgFilterLoad.BtcDecode", str)
}
msg.Filter = make([]byte, size)
_, err = io.ReadFull(r, msg.Filter)
if err != nil {
return err
}
err = readElements(r, &msg.HashFuncs, &msg.Tweak, &msg.Flags)
if err != nil {
return err
}
if msg.HashFuncs > MaxFilterLoadHashFuncs {
str := fmt.Sprintf("too many filter hash functions for message "+
"[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs)
return messageError("MsgFilterLoad.BtcDecode", str)
}
return nil
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgFilterLoad) BtcEncode(w io.Writer, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("filterload message invalid for protocol "+
"version %d", pver)
return messageError("MsgFilterLoad.BtcEncode", str)
}
size := len(msg.Filter)
if size > MaxFilterLoadFilterSize {
str := fmt.Sprintf("filterload filter size too large for message "+
"[size %v, max %v]", size, MaxFilterLoadFilterSize)
return messageError("MsgFilterLoad.BtcEncode", str)
}
if msg.HashFuncs > MaxFilterLoadHashFuncs {
str := fmt.Sprintf("too many filter hash functions for message "+
"[count %v, max %v]", msg.HashFuncs, MaxFilterLoadHashFuncs)
return messageError("MsgFilterLoad.BtcEncode", str)
}
err := writeVarInt(w, pver, uint64(size))
if err != nil {
return err
}
err = writeElements(w, msg.Filter, msg.HashFuncs, msg.Tweak, msg.Flags)
if err != nil {
return err
}
return nil
}
// Serialize encodes the transaction to w using a format that suitable for
// long-term storage such as a database while respecting the Version field in
// the transaction. This function differs from BtcEncode in that BtcEncode
// encodes the transaction to the bitcoin wire protocol in order to be sent
// across the network. The wire encoding can technically differ depending on
// the protocol version and doesn't even really need to match the format of a
// stored transaction at all. As of the time this comment was written, the
// encoded transaction is the same in both instances, but there is a distinct
// difference and separating the two allows the API to be flexible enough to
// deal with changes.
func (msg *MsgFilterLoad) Serialize(w io.Writer) error {
// At the current time, there is no difference between the wire encoding
// at protocol version 0 and the stable long-term storage format. As
// a result, make use of BtcEncode.
return msg.BtcEncode(w, BIP0037Version)
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgFilterLoad) Command() string {
return cmdFilterLoad
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgFilterLoad) MaxPayloadLength(pver uint32) uint32 {
return MaxVarIntPayload + MaxFilterLoadFilterSize + 4 + 4 + 1
}
// NewMsgFilterLoad returns a new bitcoin filterload message that conforms to the Message
// interface. See MsgFilterLoad for details.
func NewMsgFilterLoad(filter []byte, hashFuncs uint32, tweak uint32, flags BloomUpdateType) *MsgFilterLoad {
return &MsgFilterLoad{
Filter: filter,
HashFuncs: hashFuncs,
Tweak: tweak,
Flags: flags,
}
}

223
msgfilterload_test.go Normal file
View file

@ -0,0 +1,223 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire_test
import (
"bytes"
"github.com/conformal/btcwire"
"io"
"testing"
)
// TestFilterCLearLatest tests the MsgFilterLoad API against the latest protocol version.
func TestFilterLoadLatest(t *testing.T) {
pver := btcwire.ProtocolVersion
data := []byte{0x01, 0x02}
msg := btcwire.NewMsgFilterLoad(data, 10, 0, 0)
// Ensure the command is expected value.
wantCmd := "filterload"
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgFilterLoad: wrong command - got %v want %v",
cmd, wantCmd)
}
// Ensure max payadd is expected value for latest protocol version.
wantPayload := uint32(36018)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayLoadLength: wrong max payload length for "+
"protocol version %d - got %v, want %v", pver,
maxPayload, wantPayload)
}
// Test encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, pver)
if err != nil {
t.Errorf("encode of MsgFilterLoad failed %v err <%v>", msg, err)
}
// Test decode with latest protocol version.
readmsg := btcwire.MsgFilterLoad{}
err = readmsg.BtcDecode(&buf, pver)
if err != nil {
t.Errorf("decode of MsgFilterLoad failed [%v] err <%v>", buf, err)
}
return
}
// TestFilterLoadCrossProtocol tests the MsgFilterLoad API when encoding with the latest
// protocol version and decoded with BIP0031Version.
func TestFilterLoadCrossProtocol(t *testing.T) {
data := []byte{0x01, 0x02}
msg := btcwire.NewMsgFilterLoad(data, 10, 0, 0)
// Encode with old protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.BIP0037Version-1)
if err == nil {
t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
// Decode with old protocol version.
readmsg := btcwire.MsgFilterLoad{}
err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version)
if err == nil {
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
}
// TestFilterLoadMaxFilterSize tests the MsgFilterLoad API maximum filter size.
func TestFilterLoadMaxFilterSize(t *testing.T) {
data := bytes.Repeat([]byte{0xff}, 36001)
msg := btcwire.NewMsgFilterLoad(data, 10, 0, 0)
// Encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
// Decode with latest protocol version.
readbuf := bytes.NewReader(data)
err = msg.BtcDecode(readbuf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
}
// TestFilterLoadMaxHashFuncsSize tests the MsgFilterLoad API maximum hash functions.
func TestFilterLoadMaxHashFuncsSize(t *testing.T) {
data := bytes.Repeat([]byte{0xff}, 10)
msg := btcwire.NewMsgFilterLoad(data, 61, 0, 0)
// Encode with latest protocol version.
var buf bytes.Buffer
err := msg.BtcEncode(&buf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("encode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
newBuf := []byte{
0x0a, // filter size
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // filter
0x3d, 0x00, 0x00, 0x00, // max hash funcs
0x00, 0x00, 0x00, 0x00, // tweak
0x00, // update Type
}
// Decode with latest protocol version.
buf1 := bytes.NewBuffer(newBuf)
readbuf := bytes.NewReader(buf1.Bytes())
err = msg.BtcDecode(readbuf, btcwire.ProtocolVersion)
if err == nil {
t.Errorf("decode of MsgFilterLoad succeeded when it shouldn't have %v",
msg)
}
}
// TestFilterLoadWireErrors performs negative tests against wire encode and decode
// of MsgFilterLoad to confirm error paths work correctly.
func TestFilterLoadWireErrors(t *testing.T) {
pver := btcwire.ProtocolVersion
tests := []struct {
in *btcwire.MsgFilterLoad // Value to encode
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
max int // Max size of fixed buffer to induce errors
writeErr error // Expected write error
readErr error // Expected read error
}{
// Latest protocol version with intentional read/write errors.
{
&btcwire.MsgFilterLoad{
Filter: []byte{0x01, 0x02, 0x03, 0x04},
HashFuncs: 10,
Tweak: 0,
Flags: btcwire.BloomUpdateNone,
},
[]byte{
0x04, // Varint for size of Filter
0x01, 0x02, 0x03, 0x04, // Filter
0x00, 0x0a, // HashFuncs
0x00, 0x00, // Tweak
0x00, // Flags
},
pver,
2,
io.ErrShortWrite,
io.ErrUnexpectedEOF,
},
{
&btcwire.MsgFilterLoad{
Filter: []byte{0x01, 0x02, 0x03, 0x04},
HashFuncs: 10,
Tweak: 0,
Flags: btcwire.BloomUpdateNone,
},
[]byte{
0x04, // Varint for size of Filter
0x01, 0x02, 0x03, 0x04, // Filter
0x00, 0x0a, // HashFuncs
0x00, 0x00, // Tweak
0x00, // Flags
},
pver,
0,
io.ErrShortWrite,
io.EOF,
},
{
&btcwire.MsgFilterLoad{
Filter: []byte{0x01, 0x02, 0x03, 0x04},
HashFuncs: 10,
Tweak: 0,
Flags: btcwire.BloomUpdateNone,
},
[]byte{
0x04, // Varint for size of Filter
0x01, 0x02, 0x03, 0x04, // Filter
0x00, 0x0a, // HashFuncs
0x00, 0x00, // Tweak
0x00, // Flags
},
pver,
10,
io.ErrShortWrite,
io.ErrUnexpectedEOF,
},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Encode to wire format.
w := newFixedWriter(test.max)
err := test.in.BtcEncode(w, test.pver)
if err != test.writeErr {
t.Errorf("BtcEncode #%d wrong error got: %v, want: %v",
i, err, test.writeErr)
continue
}
// Decode from wire format.
var msg btcwire.MsgFilterLoad
r := newFixedReader(test.max, test.buf)
err = msg.BtcDecode(r, test.pver)
if err != test.readErr {
t.Errorf("BtcDecode #%d wrong error got: %v, want: %v",
i, err, test.readErr)
continue
}
}
}

163
msgmerkleblock.go Normal file
View file

@ -0,0 +1,163 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire
import (
"fmt"
"io"
)
// MsgMerkleBlock implements the Message interface and represents a bitcoin merkleblock
// message which is used to reset a Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgMerkleBlock struct {
Header BlockHeader
Transactions uint32
Hashes []*ShaHash
Flags []byte
}
// AddTxHash adds a new transaction hash to the message.
func (msg *MsgMerkleBlock) AddTxHash(hash *ShaHash) error {
if len(msg.Hashes)+1 > maxTxPerBlock {
str := fmt.Sprintf("too many tx hashes for message [max %v]",
maxTxPerBlock)
return messageError("MsgMerkleBlock.AddTxHash", str)
}
msg.Hashes = append(msg.Hashes, hash)
return nil
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("merkleblock message invalid for protocol "+
"version %d", pver)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
err := readBlockHeader(r, pver, &msg.Header)
if err != nil {
return err
}
err = readElement(r, &msg.Transactions)
if err != nil {
return err
}
// Read num block locator hashes and limit to max.
count, err := readVarInt(r, pver)
if err != nil {
return err
}
if count > maxTxPerBlock {
str := fmt.Sprintf("too many transaction hashes for message "+
"[count %v, max %v]", count, maxTxPerBlock)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
msg.Hashes = make([]*ShaHash, 0, count)
for i := uint64(0); i < count; i++ {
sha := ShaHash{}
err := readElement(r, &sha)
if err != nil {
return err
}
msg.AddTxHash(&sha)
}
count, err = readVarInt(r, pver)
if err != nil {
return err
}
msg.Flags = make([]byte, 0, count)
err = readElement(r, &msg.Flags)
if err != nil {
return err
}
return nil
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32) error {
if pver < BIP0037Version {
str := fmt.Sprintf("merkleblock message invalid for protocol "+
"version %d", pver)
return messageError("MsgMerkleBlock.BtcEncode", str)
}
// Read num transaction hashes and limit to max.
count := len(msg.Hashes)
if count > maxTxPerBlock {
str := fmt.Sprintf("too many transaction hashes for message "+
"[count %v, max %v]", count, maxTxPerBlock)
return messageError("MsgMerkleBlock.BtcDecode", str)
}
err := writeBlockHeader(w, pver, &msg.Header)
if err != nil {
return err
}
err = writeElement(w, msg.Transactions)
if err != nil {
return err
}
err = writeVarInt(w, pver, uint64(count))
if err != nil {
return err
}
for _, hash := range msg.Hashes {
err = writeElement(w, hash)
if err != nil {
return err
}
}
count = len(msg.Flags)
err = writeVarInt(w, pver, uint64(count))
if err != nil {
return err
}
err = writeElement(w, msg.Flags)
if err != nil {
return err
}
return nil
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgMerkleBlock) Command() string {
return cmdMerkleBlock
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 {
return MaxBlockPayload
}
// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to the Message
// interface. See MsgMerkleBlock for details.
func NewMsgMerkleBlock(bh *BlockHeader) *MsgMerkleBlock {
return &MsgMerkleBlock{
Header: *bh,
Transactions: 0,
Hashes: make([]*ShaHash, 0),
Flags: make([]byte, 0),
}
}

109
msgmerkleblock_test.go Normal file
View file

@ -0,0 +1,109 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcwire_test
import (
"bytes"
"crypto/rand"
"github.com/conformal/btcwire"
"testing"
)
// TestMerkleBlock tests the MsgMerkleBlock API.
func TestMerkleBlock(t *testing.T) {
pver := btcwire.ProtocolVersion
pverOld := btcwire.BIP0037Version - 1
// 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 := "merkleblock"
msg := btcwire.NewMsgMerkleBlock(bh)
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgBlock: wrong command - got %v want %v",
cmd, wantCmd)
}
// Ensure max payload is expected value for latest protocol version.
// Num addresses (varInt) + max allowed addresses.
wantPayload := uint32(1000000)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+
"protocol version %d - got %v, want %v", pver,
maxPayload, wantPayload)
}
// Load maxTxPerBlock hashes
data := make([]byte, 32)
for i := 0; i < (btcwire.MaxBlockPayload/10)+1; i++ {
rand.Read(data)
hash, err := btcwire.NewShaHash(data)
if err != nil {
t.Errorf("NewShaHash failed: %v\n", err)
return
}
if err = msg.AddTxHash(hash); err != nil {
t.Errorf("AddTxHash failed: %v\n", err)
return
}
}
// Add one more Tx to test failure
rand.Read(data)
hash, err := btcwire.NewShaHash(data)
if err != nil {
t.Errorf("NewShaHash failed: %v\n", err)
return
}
if err = msg.AddTxHash(hash); err == nil {
t.Errorf("AddTxHash succeeded when it should have failed")
return
}
// Test encode with latest protocol version.
var buf bytes.Buffer
err = msg.BtcEncode(&buf, pver)
if err != nil {
t.Errorf("encode of MsgMerkleBlock failed %v err <%v>", msg, err)
}
// Test encode with old protocol version.
if err = msg.BtcEncode(&buf, pverOld); err == nil {
t.Errorf("encode of MsgMerkleBlock succeeded with old protocol " +
"version when it should have failed")
return
}
// Test decode with latest protocol version.
readmsg := btcwire.MsgMerkleBlock{}
err = readmsg.BtcDecode(&buf, pver)
if err != nil {
t.Errorf("decode of MsgMerkleBlock failed [%v] err <%v>", buf, err)
}
// Test decode with old protocol version.
if err = readmsg.BtcDecode(&buf, pverOld); err == nil {
t.Errorf("decode of MsgMerkleBlock successed with old protocol " +
"version when it should have failed")
return
}
// Force extra hash to test maxTxPerBlock
msg.Hashes = append(msg.Hashes, hash)
err = msg.BtcEncode(&buf, pver)
if err == nil {
t.Errorf("encode of MsgMerkleBlock succeeded with too many tx hashes " +
"when it should have failed")
return
}
}