diff --git a/peer/peer.go b/peer/peer.go index 4b1c3a53..64a6e759 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -130,6 +130,10 @@ type MessageListeners struct { // message. OnCFHeaders func(p *Peer, msg *wire.MsgCFHeaders) + // OnCFCheckpt is invoked when a peer receives a cfcheckpt bitcoin + // message. + OnCFCheckpt func(p *Peer, msg *wire.MsgCFCheckpt) + // OnInv is invoked when a peer receives an inv bitcoin message. OnInv func(p *Peer, msg *wire.MsgInv) diff --git a/wire/message.go b/wire/message.go index f055801e..4f03cf56 100644 --- a/wire/message.go +++ b/wire/message.go @@ -56,6 +56,7 @@ const ( CmdGetCFCheckpt = "getcfcheckpt" CmdCFilter = "cfilter" CmdCFHeaders = "cfheaders" + CmdCFCheckpt = "cfcheckpt" ) // MessageEncoding represents the wire message encoding format to be used. @@ -176,6 +177,9 @@ func makeEmptyMessage(command string) (Message, error) { case CmdCFHeaders: msg = &MsgCFHeaders{} + case CmdCFCheckpt: + msg = &MsgCFCheckpt{} + default: return nil, fmt.Errorf("unhandled command [%s]", command) } diff --git a/wire/message_test.go b/wire/message_test.go index 24a01cd8..a174c49a 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -75,6 +75,7 @@ func TestMessage(t *testing.T) { msgCFilter := NewMsgCFilter(GCSFilterExtended, &chainhash.Hash{}, []byte("payload")) msgCFHeaders := NewMsgCFHeaders() + msgCFCheckpt := NewMsgCFCheckpt(GCSFilterExtended, &chainhash.Hash{}, 0) tests := []struct { in Message // Value to encode @@ -109,6 +110,7 @@ func TestMessage(t *testing.T) { {msgGetCFCheckpt, msgGetCFCheckpt, pver, MainNet, 57}, {msgCFilter, msgCFilter, pver, MainNet, 65}, {msgCFHeaders, msgCFHeaders, pver, MainNet, 90}, + {msgCFCheckpt, msgCFCheckpt, pver, MainNet, 58}, } t.Logf("Running %d tests", len(tests)) diff --git a/wire/msgcfcheckpt.go b/wire/msgcfcheckpt.go new file mode 100644 index 00000000..179abe66 --- /dev/null +++ b/wire/msgcfcheckpt.go @@ -0,0 +1,147 @@ +// Copyright (c) 2018 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" + + "github.com/roasbeef/btcd/chaincfg/chainhash" +) + +const ( + CFCheckptInterval = 1000 +) + +// MsgCFCheckpt implements the Message interface and represents a bitcoin +// cfcheckpt message. It is used to deliver committed filter header information +// in response to a getcfcheckpt message (MsgGetCFCheckpt). See MsgGetCFCheckpt +// for details on requesting the headers. +type MsgCFCheckpt struct { + FilterType FilterType + StopHash chainhash.Hash + FilterHeaders []*chainhash.Hash +} + +// AddCFHeader adds a new committed filter header to the message. +func (msg *MsgCFCheckpt) AddCFHeader(header *chainhash.Hash) error { + if len(msg.FilterHeaders) == cap(msg.FilterHeaders) { + str := fmt.Sprintf("FilterHeaders has insufficient capacity for "+ + "additional header: len = %d", len(msg.FilterHeaders)) + return messageError("MsgCFCheckpt.AddCFHeader", str) + } + + msg.FilterHeaders = append(msg.FilterHeaders, header) + return nil +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error { + // Read filter type + err := readElement(r, &msg.FilterType) + if err != nil { + return err + } + + // Read stop hash + err = readElement(r, &msg.StopHash) + if err != nil { + return err + } + + // Read number of filter headers + count, err := ReadVarInt(r, pver) + if err != nil { + return err + } + + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + msg.FilterHeaders = make([]*chainhash.Hash, count, count) + for i := uint64(0); i < count; i++ { + var cfh chainhash.Hash + err := readElement(r, &cfh) + if err != nil { + return err + } + msg.FilterHeaders[i] = &cfh + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgCFCheckpt) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error { + // Write filter type + err := writeElement(w, msg.FilterType) + if err != nil { + return err + } + + // Write stop hash + err = writeElement(w, msg.StopHash) + if err != nil { + return err + } + + // Write length of FilterHeaders slice + count := len(msg.FilterHeaders) + err = WriteVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, cfh := range msg.FilterHeaders { + err := writeElement(w, cfh) + if err != nil { + return err + } + } + + return nil +} + +// Deserialize decodes a filter header from r into the receiver using a format +// that is suitable for long-term storage such as a database. This function +// differs from BtcDecode in that BtcDecode decodes from the bitcoin wire +// protocol as it was sent across the network. The wire encoding can +// technically differ depending on the protocol version and doesn't even really +// need to match the format of a stored filter header at all. As of the time +// this comment was written, the encoded filter header 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 *MsgCFCheckpt) Deserialize(r io.Reader) error { + // At the current time, there is no difference between the wire encoding + // and the stable long-term storage format. As a result, make use of + // BtcDecode. + return msg.BtcDecode(r, 0, BaseEncoding) +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgCFCheckpt) Command() string { + return CmdCFCheckpt +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgCFCheckpt) MaxPayloadLength(pver uint32) uint32 { + // Message size depends on the blockchain height, so return general limit + // for all messages. + return MaxMessagePayload +} + +// NewMsgCFCheckpt returns a new bitcoin cfheaders message that conforms to +// the Message interface. See MsgCFCheckpt for details. +func NewMsgCFCheckpt(filterType FilterType, stopHash *chainhash.Hash, + headersCount int) *MsgCFCheckpt { + return &MsgCFCheckpt{ + FilterType: filterType, + StopHash: *stopHash, + FilterHeaders: make([]*chainhash.Hash, 0, headersCount), + } +}