From ca4e9b82d639837e4d7b2071c9c32c0e54f49a4c Mon Sep 17 00:00:00 2001 From: David Hill Date: Thu, 25 Aug 2016 15:08:32 -0400 Subject: [PATCH] wire: implement feefilter message (bip0133) feefilter is used to request the receiving peer does not announce any transactions below the specified minimum fee rate. --- wire/doc.go | 1 + wire/message.go | 4 + wire/msgfeefilter.go | 74 +++++++++++++++ wire/msgfeefilter_test.go | 192 ++++++++++++++++++++++++++++++++++++++ wire/protocol.go | 12 ++- 5 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 wire/msgfeefilter.go create mode 100644 wire/msgfeefilter_test.go diff --git a/wire/doc.go b/wire/doc.go index c10373ee..b8b8c56f 100644 --- a/wire/doc.go +++ b/wire/doc.go @@ -157,5 +157,6 @@ This package includes spec changes outlined by the following BIPs: BIP0037 (https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki) BIP0111 (https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki) BIP0130 (https://github.com/bitcoin/bips/blob/master/bip-0130.mediawiki) + BIP0133 (https://github.com/bitcoin/bips/blob/master/bip-0133.mediawiki) */ package wire diff --git a/wire/message.go b/wire/message.go index 9cded98c..d1b684f7 100644 --- a/wire/message.go +++ b/wire/message.go @@ -50,6 +50,7 @@ const ( CmdMerkleBlock = "merkleblock" CmdReject = "reject" CmdSendHeaders = "sendheaders" + CmdFeeFilter = "feefilter" ) // Message is an interface that describes a bitcoin message. A type that @@ -134,6 +135,9 @@ func makeEmptyMessage(command string) (Message, error) { case CmdSendHeaders: msg = &MsgSendHeaders{} + case CmdFeeFilter: + msg = &MsgFeeFilter{} + default: return nil, fmt.Errorf("unhandled command [%s]", command) } diff --git a/wire/msgfeefilter.go b/wire/msgfeefilter.go new file mode 100644 index 00000000..198f9b03 --- /dev/null +++ b/wire/msgfeefilter.go @@ -0,0 +1,74 @@ +// Copyright (c) 2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "fmt" + "io" +) + +// MsgFeeFilter implements the Message interface and represents a bitcoin +// feefilter message. It is used to request the receiving peer does not +// announce any transactions below the specified minimum fee rate. +// +// This message was not added until protocol versions starting with +// FeeFilterVersion. +type MsgFeeFilter struct { + MinFee int64 +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgFeeFilter) BtcDecode(r io.Reader, pver uint32) error { + if pver < FeeFilterVersion { + str := fmt.Sprintf("feefilter message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFeeFilter.BtcDecode", str) + } + + err := readElement(r, &msg.MinFee) + 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 *MsgFeeFilter) BtcEncode(w io.Writer, pver uint32) error { + if pver < FeeFilterVersion { + str := fmt.Sprintf("feefilter message invalid for protocol "+ + "version %d", pver) + return messageError("MsgFeeFilter.BtcEncode", str) + } + + err := writeElement(w, msg.MinFee) + 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 *MsgFeeFilter) Command() string { + return CmdFeeFilter +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgFeeFilter) MaxPayloadLength(pver uint32) uint32 { + return 8 +} + +// NewMsgFeeFilter returns a new bitcoin feefilter message that conforms to +// the Message interface. See MsgFeeFilter for details. +func NewMsgFeeFilter(minfee int64) *MsgFeeFilter { + return &MsgFeeFilter{ + MinFee: minfee, + } +} diff --git a/wire/msgfeefilter_test.go b/wire/msgfeefilter_test.go new file mode 100644 index 00000000..3159bfcb --- /dev/null +++ b/wire/msgfeefilter_test.go @@ -0,0 +1,192 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "bytes" + "io" + "math/rand" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// TestFeeFilterLatest tests the MsgFeeFilter API against the latest protocol version. +func TestFeeFilterLatest(t *testing.T) { + pver := ProtocolVersion + + minfee := rand.Int63() + msg := NewMsgFeeFilter(minfee) + if msg.MinFee != minfee { + t.Errorf("NewMsgFeeFilter: wrong minfee - got %v, want %v", + msg.MinFee, minfee) + } + + // Ensure the command is expected value. + wantCmd := "feefilter" + if cmd := msg.Command(); cmd != wantCmd { + t.Errorf("NewMsgFeeFilter: 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 MsgFeeFilter failed %v err <%v>", msg, err) + } + + // Test decode with latest protocol version. + readmsg := NewMsgFeeFilter(0) + err = readmsg.BtcDecode(&buf, pver) + if err != nil { + t.Errorf("decode of MsgFeeFilter failed [%v] err <%v>", buf, err) + } + + // Ensure minfee is the same. + if msg.MinFee != readmsg.MinFee { + t.Errorf("Should get same minfee for protocol version %d", pver) + } + + return +} + +// TestFeeFilterWire tests the MsgFeeFilter wire encode and decode for various protocol +// versions. +func TestFeeFilterWire(t *testing.T) { + tests := []struct { + in MsgFeeFilter // Message to encode + out MsgFeeFilter // Expected decoded message + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + }{ + // Latest protocol version. + { + MsgFeeFilter{MinFee: 123123}, // 0x1e0f3 + MsgFeeFilter{MinFee: 123123}, // 0x1e0f3 + []byte{0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}, + ProtocolVersion, + }, + + // Protocol version FeeFilterVersion + { + MsgFeeFilter{MinFee: 456456}, // 0x6f708 + MsgFeeFilter{MinFee: 456456}, // 0x6f708 + []byte{0x08, 0xf7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + FeeFilterVersion, + }, + } + + 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 MsgFeeFilter + rbuf := bytes.NewReader(test.buf) + err = msg.BtcDecode(rbuf, test.pver) + if err != nil { + t.Errorf("BtcDecode #%d error %v", i, err) + continue + } + if !reflect.DeepEqual(msg, test.out) { + t.Errorf("BtcDecode #%d\n got: %s want: %s", i, + spew.Sdump(msg), spew.Sdump(test.out)) + continue + } + } +} + +// TestFeeFilterWireErrors performs negative tests against wire encode and decode +// of MsgFeeFilter to confirm error paths work correctly. +func TestFeeFilterWireErrors(t *testing.T) { + pver := ProtocolVersion + pverNoFeeFilter := FeeFilterVersion - 1 + wireErr := &MessageError{} + + baseFeeFilter := NewMsgFeeFilter(123123) // 0x1e0f3 + baseFeeFilterEncoded := []byte{ + 0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + tests := []struct { + in *MsgFeeFilter // Value to encode + buf []byte // Wire encoding + pver uint32 // Protocol version for wire encoding + max int // Max size of fixed buffer to induce errors + writeErr error // Expected write error + readErr error // Expected read error + }{ + // Latest protocol version with intentional read/write errors. + // Force error in minfee. + {baseFeeFilter, baseFeeFilterEncoded, pver, 0, io.ErrShortWrite, io.EOF}, + // Force error due to unsupported protocol version. + {baseFeeFilter, baseFeeFilterEncoded, pverNoFeeFilter, 4, wireErr, wireErr}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + // Encode to wire format. + w := newFixedWriter(test.max) + err := test.in.BtcEncode(w, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { + t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", + i, err, test.writeErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.writeErr { + t.Errorf("BtcEncode #%d wrong error got: %v, "+ + "want: %v", i, err, test.writeErr) + continue + } + } + + // Decode from wire format. + var msg MsgFeeFilter + r := newFixedReader(test.max, test.buf) + err = msg.BtcDecode(r, test.pver) + if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { + t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", + i, err, test.readErr) + continue + } + + // For errors which are not of type MessageError, check them for + // equality. + if _, ok := err.(*MessageError); !ok { + if err != test.readErr { + t.Errorf("BtcDecode #%d wrong error got: %v, "+ + "want: %v", i, err, test.readErr) + continue + } + } + + } +} diff --git a/wire/protocol.go b/wire/protocol.go index ba2c209f..d73850dc 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -12,7 +12,7 @@ import ( const ( // ProtocolVersion is the latest protocol version this package supports. - ProtocolVersion uint32 = 70012 + ProtocolVersion uint32 = 70013 // MultipleAddressVersion is the protocol version which added multiple // addresses per message (pver >= MultipleAddressVersion). @@ -35,6 +35,10 @@ const ( // with a relay flag (pver >= BIP0037Version). BIP0037Version uint32 = 70001 + // RejectVersion is the protocol version which added a new reject + // message. + RejectVersion uint32 = 70002 + // BIP0111Version is the protocol version which added the SFNodeBloom // service flag. BIP0111Version uint32 = 70011 @@ -43,9 +47,9 @@ const ( // sendheaders message. SendHeadersVersion uint32 = 70012 - // RejectVersion is the protocol version which added a new reject - // message. - RejectVersion uint32 = 70002 + // FeeFilterVersion is the protocol version which added a new + // feefilter message. + FeeFilterVersion uint32 = 70013 ) // ServiceFlag identifies services supported by a bitcoin peer.