diff --git a/peer/peer.go b/peer/peer.go index 858e2fc6..0a3765d7 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -126,9 +126,9 @@ type MessageListeners struct { // OnCFilter is invoked when a peer receives a cfilter bitcoin message. OnCFilter func(p *Peer, msg *wire.MsgCFilter) - // OnCFHeader is invoked when a peer receives a cfheader bitcoin + // OnCFHeaders is invoked when a peer receives a cfheader bitcoin // message. - OnCFHeader func(p *Peer, msg *wire.MsgCFHeader) + OnCFHeaders func(p *Peer, msg *wire.MsgCFHeaders) // OnInv is invoked when a peer receives an inv bitcoin message. OnInv func(p *Peer, msg *wire.MsgInv) @@ -155,9 +155,9 @@ type MessageListeners struct { // message. OnGetCFilter func(p *Peer, msg *wire.MsgGetCFilter) - // OnGetCFHeader is invoked when a peer receives a getcfheader + // OnGetCFHeaders is invoked when a peer receives a getcfheader // bitcoin message. - OnGetCFHeader func(p *Peer, msg *wire.MsgGetCFHeader) + OnGetCFHeaders func(p *Peer, msg *wire.MsgGetCFHeaders) // OnFeeFilter is invoked when a peer receives a feefilter bitcoin message. OnFeeFilter func(p *Peer, msg *wire.MsgFeeFilter) @@ -1599,9 +1599,9 @@ out: p.cfg.Listeners.OnGetCFilter(p, msg) } - case *wire.MsgGetCFHeader: - if p.cfg.Listeners.OnGetCFHeader != nil { - p.cfg.Listeners.OnGetCFHeader(p, msg) + case *wire.MsgGetCFHeaders: + if p.cfg.Listeners.OnGetCFHeaders != nil { + p.cfg.Listeners.OnGetCFHeaders(p, msg) } case *wire.MsgCFilter: @@ -1609,9 +1609,9 @@ out: p.cfg.Listeners.OnCFilter(p, msg) } - case *wire.MsgCFHeader: - if p.cfg.Listeners.OnCFHeader != nil { - p.cfg.Listeners.OnCFHeader(p, msg) + case *wire.MsgCFHeaders: + if p.cfg.Listeners.OnCFHeaders != nil { + p.cfg.Listeners.OnCFHeaders(p, msg) } case *wire.MsgFeeFilter: diff --git a/peer/peer_test.go b/peer/peer_test.go index 04e7b7bd..8f80dfb7 100644 --- a/peer/peer_test.go +++ b/peer/peer_test.go @@ -402,13 +402,13 @@ func TestPeerListeners(t *testing.T) { OnGetCFilter: func(p *peer.Peer, msg *wire.MsgGetCFilter) { ok <- msg }, - OnGetCFHeader: func(p *peer.Peer, msg *wire.MsgGetCFHeader) { + OnGetCFHeaders: func(p *peer.Peer, msg *wire.MsgGetCFHeaders) { ok <- msg }, OnCFilter: func(p *peer.Peer, msg *wire.MsgCFilter) { ok <- msg }, - OnCFHeader: func(p *peer.Peer, msg *wire.MsgCFHeader) { + OnCFHeaders: func(p *peer.Peer, msg *wire.MsgCFHeaders) { ok <- msg }, OnFeeFilter: func(p *peer.Peer, msg *wire.MsgFeeFilter) { @@ -539,16 +539,16 @@ func TestPeerListeners(t *testing.T) { wire.NewMsgGetCFilter(&chainhash.Hash{}, false), }, { - "OnGetCFHeader", - wire.NewMsgGetCFHeader(&chainhash.Hash{}, false), + "OnGetCFHeaders", + wire.NewMsgGetCFHeaders(), }, { "OnCFilter", wire.NewMsgCFilter([]byte("payload")), }, { - "OnCFHeader", - wire.NewMsgCFHeader([]byte("payload")), + "OnCFHeaders", + wire.NewMsgCFHeaders(), }, { "OnFeeFilter", diff --git a/server.go b/server.go index c1880a9b..ee6c213a 100644 --- a/server.go +++ b/server.go @@ -760,25 +760,109 @@ func (sp *serverPeer) OnGetCFilter(_ *peer.Peer, msg *wire.MsgGetCFilter) { sp.QueueMessage(filterMsg, nil) } -// OnGetCFHeader is invoked when a peer receives a getcfheader bitcoin message. -func (sp *serverPeer) OnGetCFHeader(_ *peer.Peer, msg *wire.MsgGetCFHeader) { +// OnGetCFHeaders is invoked when a peer receives a getcfheader bitcoin message. +func (sp *serverPeer) OnGetCFHeaders(_ *peer.Peer, msg *wire.MsgGetCFHeaders) { // Ignore getcfilterheader requests if not in sync. if !sp.server.blockManager.IsCurrent() { return } - headerBytes, err := sp.server.cfIndex.FilterHeaderByBlockHash( - &msg.BlockHash, msg.Extended) - - if len(headerBytes) > 0 { - peerLog.Infof("Obtained CF header for %v", msg.BlockHash) - } else { - peerLog.Infof("Could not obtain CF header for %v: %v", - msg.BlockHash, err) + // Attempt to look up the height of the provided stop hash. + chain := sp.server.blockManager.chain + endIdx := int32(math.MaxInt32) + height, err := chain.BlockHeightByHash(&msg.HashStop) + if err == nil { + endIdx = height + 1 } - headerMsg := wire.NewMsgCFHeader(headerBytes) - sp.QueueMessage(headerMsg, nil) + // There are no block locators so a specific header is being requested + // as identified by the stop hash. + if len(msg.BlockLocatorHashes) == 0 { + // No blocks with the stop hash were found so there is nothing + // to do. Just return. This behavior mirrors the reference + // implementation. + if endIdx == math.MaxInt32 { + return + } + + // Fetch the raw committed filter header bytes from the + // database. + headerBytes, err := sp.server.cfIndex.FilterHeaderByBlockHash( + &msg.HashStop, msg.Extended) + if (err != nil) || (len(headerBytes) == 0) { + peerLog.Warnf("Could not obtain CF header for %v: %v", + msg.HashStop, err) + return + } + + // Deserialize the hash. + var header chainhash.Hash + err = header.SetBytes(headerBytes) + if err != nil { + peerLog.Warnf("Committed filter header deserialize "+ + "failed: %v", err) + return + } + + headersMsg := wire.NewMsgCFHeaders() + headersMsg.AddCFHeader(&header) + sp.QueueMessage(headersMsg, nil) + return + } + + // Find the most recent known block based on the block locator. + // Use the block after the genesis block if no other blocks in the + // provided locator are known. This does mean the client will start + // over with the genesis block if unknown block locators are provided. + // This mirrors the behavior in the reference implementation. + startIdx := int32(1) + for _, hash := range msg.BlockLocatorHashes { + height, err := chain.BlockHeightByHash(hash) + if err == nil { + // Start with the next hash since we know this one. + startIdx = height + 1 + break + } + } + + // Don't attempt to fetch more than we can put into a single message. + if endIdx-startIdx > wire.MaxBlockHeadersPerMsg { + endIdx = startIdx + wire.MaxBlockHeadersPerMsg + } + + // Fetch the inventory from the block database. + hashList, err := chain.HeightRange(startIdx, endIdx) + if err != nil { + peerLog.Warnf("Header lookup failed: %v", err) + return + } + + // Generate cfheaders message and send it. + headersMsg := wire.NewMsgCFHeaders() + var header chainhash.Hash + for i := range hashList { + // Fetch the raw committed filter header bytes from the + // database. + headerBytes, err := sp.server.cfIndex.FilterHeaderByBlockHash( + &hashList[i], msg.Extended) + if (err != nil) || (len(headerBytes) == 0) { + peerLog.Warnf("Could not obtain CF header for %v: %v", + hashList[i], err) + return + } + + // Deserialize the hash. + err = header.SetBytes(headerBytes) + if err != nil { + peerLog.Warnf("Committed filter header deserialize "+ + "failed: %v", err) + return + } + + headersMsg.AddCFHeader(&header) + } + + sp.QueueMessage(headersMsg, nil) } // enforceNodeBloomFlag disconnects the peer if the server is not configured to @@ -1619,25 +1703,25 @@ func disconnectPeer(peerList map[int32]*serverPeer, compareFunc func(*serverPeer func newPeerConfig(sp *serverPeer) *peer.Config { return &peer.Config{ Listeners: peer.MessageListeners{ - OnVersion: sp.OnVersion, - OnMemPool: sp.OnMemPool, - OnTx: sp.OnTx, - OnBlock: sp.OnBlock, - OnInv: sp.OnInv, - OnHeaders: sp.OnHeaders, - OnGetData: sp.OnGetData, - OnGetBlocks: sp.OnGetBlocks, - OnGetHeaders: sp.OnGetHeaders, - OnGetCFilter: sp.OnGetCFilter, - OnGetCFHeader: sp.OnGetCFHeader, - OnFeeFilter: sp.OnFeeFilter, - OnFilterAdd: sp.OnFilterAdd, - OnFilterClear: sp.OnFilterClear, - OnFilterLoad: sp.OnFilterLoad, - OnGetAddr: sp.OnGetAddr, - OnAddr: sp.OnAddr, - OnRead: sp.OnRead, - OnWrite: sp.OnWrite, + OnVersion: sp.OnVersion, + OnMemPool: sp.OnMemPool, + OnTx: sp.OnTx, + OnBlock: sp.OnBlock, + OnInv: sp.OnInv, + OnHeaders: sp.OnHeaders, + OnGetData: sp.OnGetData, + OnGetBlocks: sp.OnGetBlocks, + OnGetHeaders: sp.OnGetHeaders, + OnGetCFilter: sp.OnGetCFilter, + OnGetCFHeaders: sp.OnGetCFHeaders, + OnFeeFilter: sp.OnFeeFilter, + OnFilterAdd: sp.OnFilterAdd, + OnFilterClear: sp.OnFilterClear, + OnFilterLoad: sp.OnFilterLoad, + OnGetAddr: sp.OnGetAddr, + OnAddr: sp.OnAddr, + OnRead: sp.OnRead, + OnWrite: sp.OnWrite, // Note: The reference client currently bans peers that send alerts // not signed with its key. We could verify against their key, but diff --git a/wire/message.go b/wire/message.go index 377b9673..9a34a2fa 100644 --- a/wire/message.go +++ b/wire/message.go @@ -28,33 +28,33 @@ const MaxMessagePayload = (1024 * 1024 * 32) // 32MB // Commands used in bitcoin message headers which describe the type of message. const ( - CmdVersion = "version" - CmdVerAck = "verack" - CmdGetAddr = "getaddr" - CmdAddr = "addr" - CmdGetBlocks = "getblocks" - CmdInv = "inv" - CmdGetData = "getdata" - CmdNotFound = "notfound" - CmdBlock = "block" - CmdTx = "tx" - CmdGetHeaders = "getheaders" - CmdHeaders = "headers" - CmdPing = "ping" - CmdPong = "pong" - CmdAlert = "alert" - CmdMemPool = "mempool" - CmdFilterAdd = "filteradd" - CmdFilterClear = "filterclear" - CmdFilterLoad = "filterload" - CmdMerkleBlock = "merkleblock" - CmdReject = "reject" - CmdSendHeaders = "sendheaders" - CmdFeeFilter = "feefilter" - CmdGetCFilter = "getcfilter" - CmdGetCFHeader = "getcfheader" - CmdCFilter = "cfilter" - CmdCFHeader = "cfheader" + CmdVersion = "version" + CmdVerAck = "verack" + CmdGetAddr = "getaddr" + CmdAddr = "addr" + CmdGetBlocks = "getblocks" + CmdInv = "inv" + CmdGetData = "getdata" + CmdNotFound = "notfound" + CmdBlock = "block" + CmdTx = "tx" + CmdGetHeaders = "getheaders" + CmdHeaders = "headers" + CmdPing = "ping" + CmdPong = "pong" + CmdAlert = "alert" + CmdMemPool = "mempool" + CmdFilterAdd = "filteradd" + CmdFilterClear = "filterclear" + CmdFilterLoad = "filterload" + CmdMerkleBlock = "merkleblock" + CmdReject = "reject" + CmdSendHeaders = "sendheaders" + CmdFeeFilter = "feefilter" + CmdGetCFilter = "getcfilter" + CmdGetCFHeaders = "getcfheaders" + CmdCFilter = "cfilter" + CmdCFHeaders = "cfheaders" ) // MessageEncoding represents the wire message encoding format to be used. @@ -163,14 +163,14 @@ func makeEmptyMessage(command string) (Message, error) { case CmdGetCFilter: msg = &MsgGetCFilter{} - case CmdGetCFHeader: - msg = &MsgGetCFHeader{} + case CmdGetCFHeaders: + msg = &MsgGetCFHeaders{} case CmdCFilter: msg = &MsgCFilter{} - case CmdCFHeader: - msg = &MsgCFHeader{} + case CmdCFHeaders: + msg = &MsgCFHeaders{} default: return nil, fmt.Errorf("unhandled command [%s]", command) diff --git a/wire/message_test.go b/wire/message_test.go index 1aad5ef7..2b5e41ef 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -70,9 +70,9 @@ func TestMessage(t *testing.T) { msgMerkleBlock := NewMsgMerkleBlock(bh) msgReject := NewMsgReject("block", RejectDuplicate, "duplicate block") msgGetCFilter := NewMsgGetCFilter(&chainhash.Hash{}, false) - msgGetCFHeader := NewMsgGetCFHeader(&chainhash.Hash{}, false) + msgGetCFHeaders := NewMsgGetCFHeaders() msgCFilter := NewMsgCFilter([]byte("payload")) - msgCFHeader := NewMsgCFHeader([]byte("payload")) + msgCFHeaders := NewMsgCFHeaders() tests := []struct { in Message // Value to encode @@ -103,9 +103,9 @@ func TestMessage(t *testing.T) { {msgMerkleBlock, msgMerkleBlock, pver, MainNet, 110}, {msgReject, msgReject, pver, MainNet, 79}, {msgGetCFilter, msgGetCFilter, pver, MainNet, 57}, - {msgGetCFHeader, msgGetCFHeader, pver, MainNet, 57}, + {msgGetCFHeaders, msgGetCFHeaders, pver, MainNet, 62}, {msgCFilter, msgCFilter, pver, MainNet, 32}, - {msgCFHeader, msgCFHeader, pver, MainNet, 32}, + {msgCFHeaders, msgCFHeaders, pver, MainNet, 25}, } t.Logf("Running %d tests", len(tests)) diff --git a/wire/msgcfheader.go b/wire/msgcfheader.go deleted file mode 100644 index f6945544..00000000 --- a/wire/msgcfheader.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2017 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/btcsuite/fastsha256" -) - -const ( - // MaxCFHeaderDataSize is the maximum byte size of a committed - // filter header. - MaxCFHeaderDataSize = fastsha256.Size -) - -type MsgCFHeader struct { - Data []byte -} - -// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. -// This is part of the Message interface implementation. -func (msg *MsgCFHeader) BtcDecode(r io.Reader, pver uint32) error { - var err error - msg.Data, err = ReadVarBytes(r, pver, MaxCFHeaderDataSize, - "cf header data") - return err -} - -// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. -// This is part of the Message interface implementation. -func (msg *MsgCFHeader) BtcEncode(w io.Writer, pver uint32) error { - size := len(msg.Data) - if size > MaxCFHeaderDataSize { - str := fmt.Sprintf("cf header size too large for message "+ - "[size %v, max %v]", size, MaxCFHeaderDataSize) - return messageError("MsgCFHeader.BtcEncode", str) - } - - return WriteVarBytes(w, pver, msg.Data) -} - -// 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 *MsgCFHeader) 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) -} - -// Command returns the protocol command string for the message. This is part -// of the Message interface implementation. -func (msg *MsgCFHeader) Command() string { - return CmdCFHeader -} - -// MaxPayloadLength returns the maximum length the payload can be for the -// receiver. This is part of the Message interface implementation. -func (msg *MsgCFHeader) MaxPayloadLength(pver uint32) uint32 { - return uint32(VarIntSerializeSize(MaxCFHeaderDataSize)) + - MaxCFHeaderDataSize -} - -// NewMsgCFHeader returns a new bitcoin cfheader message that conforms to -// the Message interface. See MsgCFHeader for details. -func NewMsgCFHeader(data []byte) *MsgCFHeader { - return &MsgCFHeader{ - Data: data, - } -} diff --git a/wire/msgcfheaders.go b/wire/msgcfheaders.go new file mode 100644 index 00000000..b66d695f --- /dev/null +++ b/wire/msgcfheaders.go @@ -0,0 +1,139 @@ +// Copyright (c) 2017 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/btcsuite/btcd/chaincfg/chainhash" +) + +const ( + // MaxCFHeaderPayload is the maximum byte size of a committed + // filter header. + MaxCFHeaderPayload = chainhash.HashSize + + // MaxCFHeadersPerMsg is the maximum number of committed filter headers + // that can be in a single bitcoin cfheaders message. + MaxCFHeadersPerMsg = 2000 +) + +// MsgCFHeaders implements the Message interface and represents a bitcoin +// cfheaders message. It is used to deliver committed filter header information +// in response to a getcfheaders message (MsgGetCFHeaders). The maximum number +// of committed filter headers per message is currently 2000. See +// MsgGetCFHeaders for details on requesting the headers. +type MsgCFHeaders struct { + HeaderHashes []*chainhash.Hash +} + +// AddCFHeader adds a new committed filter header to the message. +func (msg *MsgCFHeaders) AddCFHeader(headerHash *chainhash.Hash) error { + if len(msg.HeaderHashes)+1 > MaxCFHeadersPerMsg { + str := fmt.Sprintf("too many block headers in message [max %v]", + MaxBlockHeadersPerMsg) + return messageError("MsgCFHeaders.AddCFHeader", str) + } + + msg.HeaderHashes = append(msg.HeaderHashes, headerHash) + return nil +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32) error { + count, err := ReadVarInt(r, pver) + if err != nil { + return err + } + + // Limit to max committed filter headers per message. + if count > MaxCFHeadersPerMsg { + str := fmt.Sprintf("too many committed filter headers for "+ + "message [count %v, max %v]", count, + MaxBlockHeadersPerMsg) + return messageError("MsgCFHeaders.BtcDecode", str) + } + + // Create a contiguous slice of headers to deserialize into in order to + // reduce the number of allocations. + headers := make([]chainhash.Hash, count) + msg.HeaderHashes = make([]*chainhash.Hash, 0, count) + for i := uint64(0); i < count; i++ { + cfh := &headers[i] + err := readElement(r, &cfh) + if err != nil { + return err + } + msg.AddCFHeader(cfh) + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgCFHeaders) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max committed headers per message. + count := len(msg.HeaderHashes) + if count > MaxCFHeadersPerMsg { + str := fmt.Sprintf("too many committed filter headers for "+ + "message [count %v, max %v]", count, + MaxBlockHeadersPerMsg) + return messageError("MsgCFHeaders.BtcEncode", str) + } + + err := WriteVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, cfh := range msg.HeaderHashes { + 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 *MsgCFHeaders) 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) +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgCFHeaders) Command() string { + return CmdCFHeaders +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgCFHeaders) MaxPayloadLength(pver uint32) uint32 { + // Num headers (varInt) + (header size * max allowed headers). + return MaxVarIntPayload + (MaxCFHeaderPayload * MaxBlockHeadersPerMsg) +} + +// NewMsgCFHeaders returns a new bitcoin cfheaders message that conforms to +// the Message interface. See MsgCFHeaders for details. +func NewMsgCFHeaders() *MsgCFHeaders { + return &MsgCFHeaders{ + HeaderHashes: make([]*chainhash.Hash, 0, MaxCFHeadersPerMsg), + } +} diff --git a/wire/msggetcfheader.go b/wire/msggetcfheader.go deleted file mode 100644 index 0295ca8d..00000000 --- a/wire/msggetcfheader.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2017 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 ( - "io" - - "github.com/btcsuite/btcd/chaincfg/chainhash" -) - -type MsgGetCFHeader struct { - BlockHash chainhash.Hash - Extended bool -} - -func (msg *MsgGetCFHeader) BtcDecode(r io.Reader, pver uint32) error { - err := readElement(r, &msg.BlockHash) - if err != nil { - return err - } - return readElement(r, &msg.Extended) -} - -// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. -// This is part of the Message interface implementation. -func (msg *MsgGetCFHeader) BtcEncode(w io.Writer, pver uint32) error { - err := writeElement(w, &msg.BlockHash) - if err != nil { - return err - } - return writeElement(w, msg.Extended) -} - -// Command returns the protocol command string for the message. This is part -// of the Message interface implementation. -func (msg *MsgGetCFHeader) Command() string { - return CmdGetCFHeader -} - -// MaxPayloadLength returns the maximum length the payload can be for the -// receiver. This is part of the Message interface implementation. -func (msg *MsgGetCFHeader) MaxPayloadLength(pver uint32) uint32 { - // Block hash + Extended flag. - return chainhash.HashSize + 1 -} - -// NewMsgGetCFHeader returns a new bitcoin getcfheader message that conforms to -// the Message interface using the passed parameters and defaults for the -// remaining fields. -func NewMsgGetCFHeader(blockHash *chainhash.Hash, extended bool) *MsgGetCFHeader { - return &MsgGetCFHeader{ - BlockHash: *blockHash, - Extended: extended, - } -} diff --git a/wire/msggetcfheaders.go b/wire/msggetcfheaders.go new file mode 100644 index 00000000..e66331c1 --- /dev/null +++ b/wire/msggetcfheaders.go @@ -0,0 +1,135 @@ +// Copyright (c) 2017 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/btcsuite/btcd/chaincfg/chainhash" +) + +// MsgGetCFHeaders is a message similar to MsgGetHeaders, but for committed +// filter headers. It allows to set the Extended field to get headers in the +// chain of basic (false) or extended (true) headers. +type MsgGetCFHeaders struct { + ProtocolVersion uint32 + BlockLocatorHashes []*chainhash.Hash + HashStop chainhash.Hash + Extended bool +} + +// AddBlockLocatorHash adds a new block locator hash to the message. +func (msg *MsgGetCFHeaders) AddBlockLocatorHash(hash *chainhash.Hash) error { + if len(msg.BlockLocatorHashes)+1 > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message [max %v]", + MaxBlockLocatorsPerMsg) + return messageError("MsgGetCFHeaders.AddBlockLocatorHash", str) + } + + msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash) + return nil +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgGetCFHeaders) BtcDecode(r io.Reader, pver uint32) error { + err := readElement(r, &msg.ProtocolVersion) + if err != nil { + return err + } + + // Read num block locator hashes and limit to max. + count, err := ReadVarInt(r, pver) + if err != nil { + return err + } + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetHeaders.BtcDecode", str) + } + + // Create a contiguous slice of hashes to deserialize into in order to + // reduce the number of allocations. + locatorHashes := make([]chainhash.Hash, count) + msg.BlockLocatorHashes = make([]*chainhash.Hash, 0, count) + for i := uint64(0); i < count; i++ { + hash := &locatorHashes[i] + err := readElement(r, hash) + if err != nil { + return err + } + msg.AddBlockLocatorHash(hash) + } + + err = readElement(r, &msg.HashStop) + if err != nil { + return err + } + + return readElement(r, &msg.Extended) +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgGetCFHeaders) BtcEncode(w io.Writer, pver uint32) error { + // Limit to max block locator hashes per message. + count := len(msg.BlockLocatorHashes) + if count > MaxBlockLocatorsPerMsg { + str := fmt.Sprintf("too many block locator hashes for message "+ + "[count %v, max %v]", count, MaxBlockLocatorsPerMsg) + return messageError("MsgGetHeaders.BtcEncode", str) + } + + err := writeElement(w, msg.ProtocolVersion) + if err != nil { + return err + } + + err = WriteVarInt(w, pver, uint64(count)) + if err != nil { + return err + } + + for _, hash := range msg.BlockLocatorHashes { + err := writeElement(w, hash) + if err != nil { + return err + } + } + + err = writeElement(w, &msg.HashStop) + if err != nil { + return err + } + + return writeElement(w, msg.Extended) +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgGetCFHeaders) Command() string { + return CmdGetCFHeaders +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgGetCFHeaders) MaxPayloadLength(pver uint32) uint32 { + // Version 4 bytes + num block locator hashes (varInt) + max allowed + // block locators + hash stop + Extended flag 1 byte. + return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg * + chainhash.HashSize) + chainhash.HashSize + 1 +} + +// NewMsgGetCFHeaders returns a new bitcoin getcfheader message that conforms to +// the Message interface using the passed parameters and defaults for the +// remaining fields. +func NewMsgGetCFHeaders() *MsgGetCFHeaders { + return &MsgGetCFHeaders{ + BlockLocatorHashes: make([]*chainhash.Hash, 0, + MaxBlockLocatorsPerMsg), + } +}