mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
187 lines
5.9 KiB
Go
187 lines
5.9 KiB
Go
// 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 (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcd/chainhash/v2"
|
|
)
|
|
|
|
const (
|
|
// CFCheckptInterval is the gap (in number of blocks) between each
|
|
// filter header checkpoint.
|
|
CFCheckptInterval = 1000
|
|
|
|
// maxCFHeadersLen is the max number of filter headers we will attempt
|
|
// to decode.
|
|
maxCFHeadersLen = 100000
|
|
|
|
// maxCFCheckptPayload calculates the maximum reasonable payload size
|
|
// for CF checkpoint messages.
|
|
//
|
|
// Calculation: 1 byte (filter type) + 32 bytes (stop hash) +
|
|
// 5 bytes (max varint) + (maxCFHeadersLen * 32 bytes per hash)
|
|
maxCFCheckptPayload = 1 + 32 + 5 + (maxCFHeadersLen * 32)
|
|
)
|
|
|
|
// ErrInsaneCFHeaderCount signals that we were asked to decode an
|
|
// unreasonable number of cfilter headers.
|
|
var ErrInsaneCFHeaderCount = errors.New(
|
|
"refusing to decode unreasonable number of filter headers")
|
|
|
|
// 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 {
|
|
buf := binarySerializer.Borrow()
|
|
defer binarySerializer.Return(buf)
|
|
|
|
// Read filter type
|
|
if _, err := io.ReadFull(r, buf[:1]); err != nil {
|
|
return err
|
|
}
|
|
msg.FilterType = FilterType(buf[0])
|
|
|
|
// Read stop hash
|
|
if _, err := io.ReadFull(r, msg.StopHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read number of filter headers
|
|
count, err := ReadVarIntBuf(r, pver, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Refuse to decode an insane number of cfheaders.
|
|
if count > maxCFHeadersLen {
|
|
return ErrInsaneCFHeaderCount
|
|
}
|
|
|
|
if count == 0 {
|
|
msg.FilterHeaders = make([]*chainhash.Hash, 0)
|
|
return nil
|
|
}
|
|
|
|
// Optimize memory allocation by creating a single backing array for
|
|
// all hashes. This reduces GC pressure and improves cache locality.
|
|
hashes := make([]chainhash.Hash, count)
|
|
msg.FilterHeaders = make([]*chainhash.Hash, count)
|
|
|
|
// Now we'll read all the hashes directly into the backing array we've
|
|
// created above. We'll then point the underlying filter header hashes
|
|
// into this backing array.
|
|
for i := uint64(0); i < count; i++ {
|
|
if _, err := io.ReadFull(r, hashes[i][:]); err != nil {
|
|
return err
|
|
}
|
|
msg.FilterHeaders[i] = &hashes[i]
|
|
}
|
|
|
|
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 {
|
|
buf := binarySerializer.Borrow()
|
|
defer binarySerializer.Return(buf)
|
|
|
|
// Write filter type
|
|
buf[0] = byte(msg.FilterType)
|
|
if _, err := w.Write(buf[:1]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write stop hash
|
|
if _, err := w.Write(msg.StopHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write length of FilterHeaders slice
|
|
count := len(msg.FilterHeaders)
|
|
err := WriteVarIntBuf(w, pver, uint64(count), buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, cfh := range msg.FilterHeaders {
|
|
_, err := w.Write(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 {
|
|
// Use a more precise calculation based on the maximum number of
|
|
// filter headers we support. No no reason to read more than we'll
|
|
// process in BtcDecode.
|
|
return maxCFCheckptPayload
|
|
}
|
|
|
|
// 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 {
|
|
|
|
// We pre-allocate with an exact capacity when count is known to avoid
|
|
// slice growth during message construction.
|
|
return &MsgCFCheckpt{
|
|
FilterType: filterType,
|
|
StopHash: *stopHash,
|
|
FilterHeaders: make([]*chainhash.Hash, 0, headersCount),
|
|
}
|
|
}
|