mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 01:40:07 +01:00
684d64ad74
* Add support for wtxidrelay message. This adds support for the wtxidrelay (BIP339) message in wire. While here, add tests for sendaddrv2 and rename AddrV2Version to SendAddrV2Version in order to make the code consistent with all other messages. This diff does keep the old AddrV2Version constant for backwards compatibility. * Bump copyrights
461 lines
14 KiB
Go
461 lines
14 KiB
Go
// Copyright (c) 2013-2024 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"
|
|
"fmt"
|
|
"io"
|
|
"unicode/utf8"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
)
|
|
|
|
// MessageHeaderSize is the number of bytes in a bitcoin message header.
|
|
// Bitcoin network (magic) 4 bytes + command 12 bytes + payload length 4 bytes +
|
|
// checksum 4 bytes.
|
|
const MessageHeaderSize = 24
|
|
|
|
// CommandSize is the fixed size of all commands in the common bitcoin message
|
|
// header. Shorter commands must be zero padded.
|
|
const CommandSize = 12
|
|
|
|
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
|
// individual limits imposed by messages themselves.
|
|
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"
|
|
CmdAddrV2 = "addrv2"
|
|
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"
|
|
CmdGetCFilters = "getcfilters"
|
|
CmdGetCFHeaders = "getcfheaders"
|
|
CmdGetCFCheckpt = "getcfcheckpt"
|
|
CmdCFilter = "cfilter"
|
|
CmdCFHeaders = "cfheaders"
|
|
CmdCFCheckpt = "cfcheckpt"
|
|
CmdSendAddrV2 = "sendaddrv2"
|
|
CmdWTxIdRelay = "wtxidrelay"
|
|
)
|
|
|
|
// MessageEncoding represents the wire message encoding format to be used.
|
|
type MessageEncoding uint32
|
|
|
|
const (
|
|
// BaseEncoding encodes all messages in the default format specified
|
|
// for the Bitcoin wire protocol.
|
|
BaseEncoding MessageEncoding = 1 << iota
|
|
|
|
// WitnessEncoding encodes all messages other than transaction messages
|
|
// using the default Bitcoin wire protocol specification. For transaction
|
|
// messages, the new encoding format detailed in BIP0144 will be used.
|
|
WitnessEncoding
|
|
)
|
|
|
|
// LatestEncoding is the most recently specified encoding for the Bitcoin wire
|
|
// protocol.
|
|
var LatestEncoding = WitnessEncoding
|
|
|
|
// ErrUnknownMessage is the error returned when decoding an unknown message.
|
|
var ErrUnknownMessage = fmt.Errorf("received unknown message")
|
|
|
|
// ErrInvalidHandshake is the error returned when a peer sends us a known
|
|
// message that does not belong in the version-verack handshake.
|
|
var ErrInvalidHandshake = fmt.Errorf("invalid message during handshake")
|
|
|
|
// Message is an interface that describes a bitcoin message. A type that
|
|
// implements Message has complete control over the representation of its data
|
|
// and may therefore contain additional or fewer fields than those which
|
|
// are used directly in the protocol encoded message.
|
|
type Message interface {
|
|
BtcDecode(io.Reader, uint32, MessageEncoding) error
|
|
BtcEncode(io.Writer, uint32, MessageEncoding) error
|
|
Command() string
|
|
MaxPayloadLength(uint32) uint32
|
|
}
|
|
|
|
// makeEmptyMessage creates a message of the appropriate concrete type based
|
|
// on the command.
|
|
func makeEmptyMessage(command string) (Message, error) {
|
|
var msg Message
|
|
switch command {
|
|
case CmdVersion:
|
|
msg = &MsgVersion{}
|
|
|
|
case CmdVerAck:
|
|
msg = &MsgVerAck{}
|
|
|
|
case CmdSendAddrV2:
|
|
msg = &MsgSendAddrV2{}
|
|
|
|
case CmdWTxIdRelay:
|
|
msg = &MsgWTxIdRelay{}
|
|
|
|
case CmdGetAddr:
|
|
msg = &MsgGetAddr{}
|
|
|
|
case CmdAddr:
|
|
msg = &MsgAddr{}
|
|
|
|
case CmdAddrV2:
|
|
msg = &MsgAddrV2{}
|
|
|
|
case CmdGetBlocks:
|
|
msg = &MsgGetBlocks{}
|
|
|
|
case CmdBlock:
|
|
msg = &MsgBlock{}
|
|
|
|
case CmdInv:
|
|
msg = &MsgInv{}
|
|
|
|
case CmdGetData:
|
|
msg = &MsgGetData{}
|
|
|
|
case CmdNotFound:
|
|
msg = &MsgNotFound{}
|
|
|
|
case CmdTx:
|
|
msg = &MsgTx{}
|
|
|
|
case CmdPing:
|
|
msg = &MsgPing{}
|
|
|
|
case CmdPong:
|
|
msg = &MsgPong{}
|
|
|
|
case CmdGetHeaders:
|
|
msg = &MsgGetHeaders{}
|
|
|
|
case CmdHeaders:
|
|
msg = &MsgHeaders{}
|
|
|
|
case CmdAlert:
|
|
msg = &MsgAlert{}
|
|
|
|
case CmdMemPool:
|
|
msg = &MsgMemPool{}
|
|
|
|
case CmdFilterAdd:
|
|
msg = &MsgFilterAdd{}
|
|
|
|
case CmdFilterClear:
|
|
msg = &MsgFilterClear{}
|
|
|
|
case CmdFilterLoad:
|
|
msg = &MsgFilterLoad{}
|
|
|
|
case CmdMerkleBlock:
|
|
msg = &MsgMerkleBlock{}
|
|
|
|
case CmdReject:
|
|
msg = &MsgReject{}
|
|
|
|
case CmdSendHeaders:
|
|
msg = &MsgSendHeaders{}
|
|
|
|
case CmdFeeFilter:
|
|
msg = &MsgFeeFilter{}
|
|
|
|
case CmdGetCFilters:
|
|
msg = &MsgGetCFilters{}
|
|
|
|
case CmdGetCFHeaders:
|
|
msg = &MsgGetCFHeaders{}
|
|
|
|
case CmdGetCFCheckpt:
|
|
msg = &MsgGetCFCheckpt{}
|
|
|
|
case CmdCFilter:
|
|
msg = &MsgCFilter{}
|
|
|
|
case CmdCFHeaders:
|
|
msg = &MsgCFHeaders{}
|
|
|
|
case CmdCFCheckpt:
|
|
msg = &MsgCFCheckpt{}
|
|
|
|
default:
|
|
return nil, ErrUnknownMessage
|
|
}
|
|
return msg, nil
|
|
}
|
|
|
|
// messageHeader defines the header structure for all bitcoin protocol messages.
|
|
type messageHeader struct {
|
|
magic BitcoinNet // 4 bytes
|
|
command string // 12 bytes
|
|
length uint32 // 4 bytes
|
|
checksum [4]byte // 4 bytes
|
|
}
|
|
|
|
// readMessageHeader reads a bitcoin message header from r.
|
|
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
|
// Since readElements doesn't return the amount of bytes read, attempt
|
|
// to read the entire header into a buffer first in case there is a
|
|
// short read so the proper amount of read bytes are known. This works
|
|
// since the header is a fixed size.
|
|
var headerBytes [MessageHeaderSize]byte
|
|
n, err := io.ReadFull(r, headerBytes[:])
|
|
if err != nil {
|
|
return n, nil, err
|
|
}
|
|
hr := bytes.NewReader(headerBytes[:])
|
|
|
|
// Create and populate a messageHeader struct from the raw header bytes.
|
|
hdr := messageHeader{}
|
|
var command [CommandSize]byte
|
|
readElements(hr, &hdr.magic, &command, &hdr.length, &hdr.checksum)
|
|
|
|
// Strip trailing zeros from command string.
|
|
hdr.command = string(bytes.TrimRight(command[:], "\x00"))
|
|
|
|
return n, &hdr, nil
|
|
}
|
|
|
|
// discardInput reads n bytes from reader r in chunks and discards the read
|
|
// bytes. This is used to skip payloads when various errors occur and helps
|
|
// prevent rogue nodes from causing massive memory allocation through forging
|
|
// header length.
|
|
func discardInput(r io.Reader, n uint32) {
|
|
maxSize := uint32(10 * 1024) // 10k at a time
|
|
numReads := n / maxSize
|
|
bytesRemaining := n % maxSize
|
|
if n > 0 {
|
|
buf := make([]byte, maxSize)
|
|
for i := uint32(0); i < numReads; i++ {
|
|
io.ReadFull(r, buf)
|
|
}
|
|
}
|
|
if bytesRemaining > 0 {
|
|
buf := make([]byte, bytesRemaining)
|
|
io.ReadFull(r, buf)
|
|
}
|
|
}
|
|
|
|
// WriteMessageN writes a bitcoin Message to w including the necessary header
|
|
// information and returns the number of bytes written. This function is the
|
|
// same as WriteMessage except it also returns the number of bytes written.
|
|
func WriteMessageN(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) (int, error) {
|
|
return WriteMessageWithEncodingN(w, msg, pver, btcnet, BaseEncoding)
|
|
}
|
|
|
|
// WriteMessage writes a bitcoin Message to w including the necessary header
|
|
// information. This function is the same as WriteMessageN except it doesn't
|
|
// doesn't return the number of bytes written. This function is mainly provided
|
|
// for backwards compatibility with the original API, but it's also useful for
|
|
// callers that don't care about byte counts.
|
|
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error {
|
|
_, err := WriteMessageN(w, msg, pver, btcnet)
|
|
return err
|
|
}
|
|
|
|
// WriteMessageWithEncodingN writes a bitcoin Message to w including the
|
|
// necessary header information and returns the number of bytes written.
|
|
// This function is the same as WriteMessageN except it also allows the caller
|
|
// to specify the message encoding format to be used when serializing wire
|
|
// messages.
|
|
func WriteMessageWithEncodingN(w io.Writer, msg Message, pver uint32,
|
|
btcnet BitcoinNet, encoding MessageEncoding,
|
|
) (int, error) {
|
|
totalBytes := 0
|
|
|
|
// Enforce max command size.
|
|
var command [CommandSize]byte
|
|
cmd := msg.Command()
|
|
if len(cmd) > CommandSize {
|
|
str := fmt.Sprintf("command [%s] is too long [max %v]",
|
|
cmd, CommandSize)
|
|
return totalBytes, messageError("WriteMessage", str)
|
|
}
|
|
copy(command[:], []byte(cmd))
|
|
|
|
// Encode the message payload.
|
|
var bw bytes.Buffer
|
|
err := msg.BtcEncode(&bw, pver, encoding)
|
|
if err != nil {
|
|
return totalBytes, err
|
|
}
|
|
payload := bw.Bytes()
|
|
lenp := len(payload)
|
|
|
|
// Enforce maximum overall message payload.
|
|
if lenp > MaxMessagePayload {
|
|
str := fmt.Sprintf("message payload is too large - encoded "+
|
|
"%d bytes, but maximum message payload is %d bytes",
|
|
lenp, MaxMessagePayload)
|
|
return totalBytes, messageError("WriteMessage", str)
|
|
}
|
|
|
|
// Enforce maximum message payload based on the message type.
|
|
mpl := msg.MaxPayloadLength(pver)
|
|
if uint32(lenp) > mpl {
|
|
str := fmt.Sprintf("message payload is too large - encoded "+
|
|
"%d bytes, but maximum message payload size for "+
|
|
"messages of type [%s] is %d.", lenp, cmd, mpl)
|
|
return totalBytes, messageError("WriteMessage", str)
|
|
}
|
|
|
|
// Create header for the message.
|
|
hdr := messageHeader{}
|
|
hdr.magic = btcnet
|
|
hdr.command = cmd
|
|
hdr.length = uint32(lenp)
|
|
copy(hdr.checksum[:], chainhash.DoubleHashB(payload)[0:4])
|
|
|
|
// Encode the header for the message. This is done to a buffer
|
|
// rather than directly to the writer since writeElements doesn't
|
|
// return the number of bytes written.
|
|
hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
|
|
writeElements(hw, hdr.magic, command, hdr.length, hdr.checksum)
|
|
|
|
// Write header.
|
|
n, err := w.Write(hw.Bytes())
|
|
totalBytes += n
|
|
if err != nil {
|
|
return totalBytes, err
|
|
}
|
|
|
|
// Only write the payload if there is one, e.g., verack messages don't
|
|
// have one.
|
|
if len(payload) > 0 {
|
|
n, err = w.Write(payload)
|
|
totalBytes += n
|
|
}
|
|
|
|
return totalBytes, err
|
|
}
|
|
|
|
// ReadMessageWithEncodingN reads, validates, and parses the next bitcoin Message
|
|
// from r for the provided protocol version and bitcoin network. It returns the
|
|
// number of bytes read in addition to the parsed Message and raw bytes which
|
|
// comprise the message. This function is the same as ReadMessageN except it
|
|
// allows the caller to specify which message encoding is to to consult when
|
|
// decoding wire messages.
|
|
func ReadMessageWithEncodingN(r io.Reader, pver uint32, btcnet BitcoinNet,
|
|
enc MessageEncoding,
|
|
) (int, Message, []byte, error) {
|
|
totalBytes := 0
|
|
n, hdr, err := readMessageHeader(r)
|
|
totalBytes += n
|
|
if err != nil {
|
|
return totalBytes, nil, nil, err
|
|
}
|
|
|
|
// Enforce maximum message payload.
|
|
if hdr.length > MaxMessagePayload {
|
|
str := fmt.Sprintf("message payload is too large - header "+
|
|
"indicates %d bytes, but max message payload is %d "+
|
|
"bytes.", hdr.length, MaxMessagePayload)
|
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
|
|
|
}
|
|
|
|
// Check for messages from the wrong bitcoin network.
|
|
if hdr.magic != btcnet {
|
|
discardInput(r, hdr.length)
|
|
str := fmt.Sprintf("message from other network [%v]", hdr.magic)
|
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
|
}
|
|
|
|
// Check for malformed commands.
|
|
command := hdr.command
|
|
if !utf8.ValidString(command) {
|
|
discardInput(r, hdr.length)
|
|
str := fmt.Sprintf("invalid command %v", []byte(command))
|
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
|
}
|
|
|
|
// Create struct of appropriate message type based on the command.
|
|
msg, err := makeEmptyMessage(command)
|
|
if err != nil {
|
|
// makeEmptyMessage can only return ErrUnknownMessage and it is
|
|
// important that we bubble it up to the caller.
|
|
discardInput(r, hdr.length)
|
|
return totalBytes, nil, nil, err
|
|
}
|
|
|
|
// Check for maximum length based on the message type as a malicious client
|
|
// could otherwise create a well-formed header and set the length to max
|
|
// numbers in order to exhaust the machine's memory.
|
|
mpl := msg.MaxPayloadLength(pver)
|
|
if hdr.length > mpl {
|
|
discardInput(r, hdr.length)
|
|
str := fmt.Sprintf("payload exceeds max length - header "+
|
|
"indicates %v bytes, but max payload size for "+
|
|
"messages of type [%v] is %v.", hdr.length, command, mpl)
|
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
|
}
|
|
|
|
// Read payload.
|
|
payload := make([]byte, hdr.length)
|
|
n, err = io.ReadFull(r, payload)
|
|
totalBytes += n
|
|
if err != nil {
|
|
return totalBytes, nil, nil, err
|
|
}
|
|
|
|
// Test checksum.
|
|
checksum := chainhash.DoubleHashB(payload)[0:4]
|
|
if !bytes.Equal(checksum, hdr.checksum[:]) {
|
|
str := fmt.Sprintf("payload checksum failed - header "+
|
|
"indicates %v, but actual checksum is %v.",
|
|
hdr.checksum, checksum)
|
|
return totalBytes, nil, nil, messageError("ReadMessage", str)
|
|
}
|
|
|
|
// Unmarshal message. NOTE: This must be a *bytes.Buffer since the
|
|
// MsgVersion BtcDecode function requires it.
|
|
pr := bytes.NewBuffer(payload)
|
|
err = msg.BtcDecode(pr, pver, enc)
|
|
if err != nil {
|
|
return totalBytes, nil, nil, err
|
|
}
|
|
|
|
return totalBytes, msg, payload, nil
|
|
}
|
|
|
|
// ReadMessageN reads, validates, and parses the next bitcoin Message from r for
|
|
// the provided protocol version and bitcoin network. It returns the number of
|
|
// bytes read in addition to the parsed Message and raw bytes which comprise the
|
|
// message. This function is the same as ReadMessage except it also returns the
|
|
// number of bytes read.
|
|
func ReadMessageN(r io.Reader, pver uint32, btcnet BitcoinNet) (int, Message, []byte, error) {
|
|
return ReadMessageWithEncodingN(r, pver, btcnet, BaseEncoding)
|
|
}
|
|
|
|
// ReadMessage reads, validates, and parses the next bitcoin Message from r for
|
|
// the provided protocol version and bitcoin network. It returns the parsed
|
|
// Message and raw bytes which comprise the message. This function only differs
|
|
// from ReadMessageN in that it doesn't return the number of bytes read. This
|
|
// function is mainly provided for backwards compatibility with the original
|
|
// API, but it's also useful for callers that don't care about byte counts.
|
|
func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) {
|
|
_, msg, buf, err := ReadMessageN(r, pver, btcnet)
|
|
return msg, buf, err
|
|
}
|