1// Copyright (c) 2018 The btcsuite developers
2// Use of this source code is governed by an ISC
3// license that can be found in the LICENSE file.
4
5package wire
6
7import (
8	"errors"
9	"fmt"
10	"io"
11
12	"github.com/btcsuite/btcd/chaincfg/chainhash"
13)
14
15const (
16	// CFCheckptInterval is the gap (in number of blocks) between each
17	// filter header checkpoint.
18	CFCheckptInterval = 1000
19
20	// maxCFHeadersLen is the max number of filter headers we will attempt
21	// to decode.
22	maxCFHeadersLen = 100000
23)
24
25// ErrInsaneCFHeaderCount signals that we were asked to decode an
26// unreasonable number of cfilter headers.
27var ErrInsaneCFHeaderCount = errors.New(
28	"refusing to decode unreasonable number of filter headers")
29
30// MsgCFCheckpt implements the Message interface and represents a bitcoin
31// cfcheckpt message.  It is used to deliver committed filter header information
32// in response to a getcfcheckpt message (MsgGetCFCheckpt). See MsgGetCFCheckpt
33// for details on requesting the headers.
34type MsgCFCheckpt struct {
35	FilterType    FilterType
36	StopHash      chainhash.Hash
37	FilterHeaders []*chainhash.Hash
38}
39
40// AddCFHeader adds a new committed filter header to the message.
41func (msg *MsgCFCheckpt) AddCFHeader(header *chainhash.Hash) error {
42	if len(msg.FilterHeaders) == cap(msg.FilterHeaders) {
43		str := fmt.Sprintf("FilterHeaders has insufficient capacity for "+
44			"additional header: len = %d", len(msg.FilterHeaders))
45		return messageError("MsgCFCheckpt.AddCFHeader", str)
46	}
47
48	msg.FilterHeaders = append(msg.FilterHeaders, header)
49	return nil
50}
51
52// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
53// This is part of the Message interface implementation.
54func (msg *MsgCFCheckpt) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error {
55	// Read filter type
56	err := readElement(r, &msg.FilterType)
57	if err != nil {
58		return err
59	}
60
61	// Read stop hash
62	err = readElement(r, &msg.StopHash)
63	if err != nil {
64		return err
65	}
66
67	// Read number of filter headers
68	count, err := ReadVarInt(r, pver)
69	if err != nil {
70		return err
71	}
72
73	// Refuse to decode an insane number of cfheaders.
74	if count > maxCFHeadersLen {
75		return ErrInsaneCFHeaderCount
76	}
77
78	// Create a contiguous slice of hashes to deserialize into in order to
79	// reduce the number of allocations.
80	msg.FilterHeaders = make([]*chainhash.Hash, count)
81	for i := uint64(0); i < count; i++ {
82		var cfh chainhash.Hash
83		err := readElement(r, &cfh)
84		if err != nil {
85			return err
86		}
87		msg.FilterHeaders[i] = &cfh
88	}
89
90	return nil
91}
92
93// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
94// This is part of the Message interface implementation.
95func (msg *MsgCFCheckpt) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) error {
96	// Write filter type
97	err := writeElement(w, msg.FilterType)
98	if err != nil {
99		return err
100	}
101
102	// Write stop hash
103	err = writeElement(w, msg.StopHash)
104	if err != nil {
105		return err
106	}
107
108	// Write length of FilterHeaders slice
109	count := len(msg.FilterHeaders)
110	err = WriteVarInt(w, pver, uint64(count))
111	if err != nil {
112		return err
113	}
114
115	for _, cfh := range msg.FilterHeaders {
116		err := writeElement(w, cfh)
117		if err != nil {
118			return err
119		}
120	}
121
122	return nil
123}
124
125// Deserialize decodes a filter header from r into the receiver using a format
126// that is suitable for long-term storage such as a database. This function
127// differs from BtcDecode in that BtcDecode decodes from the bitcoin wire
128// protocol as it was sent across the network.  The wire encoding can
129// technically differ depending on the protocol version and doesn't even really
130// need to match the format of a stored filter header at all. As of the time
131// this comment was written, the encoded filter header is the same in both
132// instances, but there is a distinct difference and separating the two allows
133// the API to be flexible enough to deal with changes.
134func (msg *MsgCFCheckpt) Deserialize(r io.Reader) error {
135	// At the current time, there is no difference between the wire encoding
136	// and the stable long-term storage format.  As a result, make use of
137	// BtcDecode.
138	return msg.BtcDecode(r, 0, BaseEncoding)
139}
140
141// Command returns the protocol command string for the message.  This is part
142// of the Message interface implementation.
143func (msg *MsgCFCheckpt) Command() string {
144	return CmdCFCheckpt
145}
146
147// MaxPayloadLength returns the maximum length the payload can be for the
148// receiver. This is part of the Message interface implementation.
149func (msg *MsgCFCheckpt) MaxPayloadLength(pver uint32) uint32 {
150	// Message size depends on the blockchain height, so return general limit
151	// for all messages.
152	return MaxMessagePayload
153}
154
155// NewMsgCFCheckpt returns a new bitcoin cfheaders message that conforms to
156// the Message interface. See MsgCFCheckpt for details.
157func NewMsgCFCheckpt(filterType FilterType, stopHash *chainhash.Hash,
158	headersCount int) *MsgCFCheckpt {
159	return &MsgCFCheckpt{
160		FilterType:    filterType,
161		StopHash:      *stopHash,
162		FilterHeaders: make([]*chainhash.Hash, 0, headersCount),
163	}
164}
165