291 lines
8.7 KiB
Go
291 lines
8.7 KiB
Go
// Copyright (c) 2020-2021 The Decred 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/decred/dcrd/chaincfg/chainhash"
|
|
)
|
|
|
|
// MaxISBlocksAtHeadPerMsg is the maximum number of block hashes allowed per
|
|
// message.
|
|
const MaxISBlocksAtHeadPerMsg = 8
|
|
|
|
// MaxISVotesAtHeadPerMsg is the maximum number of votes at head per message.
|
|
const MaxISVotesAtHeadPerMsg = 40 // 8 * 5
|
|
|
|
// MaxISTSpendsAtHeadPerMsg is the maximum number of tspends at head per
|
|
// message.
|
|
const MaxISTSpendsAtHeadPerMsg = 7
|
|
|
|
// MsgInitState implements the Message interface and represents an initial
|
|
// state message. It is used to receive ephemeral startup information from a
|
|
// remote peer, such as blocks that can be mined upon, votes for such blocks
|
|
// and tspends.
|
|
//
|
|
// The content of such a message depends upon what the local peer requested
|
|
// during a previous GetInitState msg.
|
|
type MsgInitState struct {
|
|
BlockHashes []chainhash.Hash
|
|
VoteHashes []chainhash.Hash
|
|
TSpendHashes []chainhash.Hash
|
|
}
|
|
|
|
// AddBlockHash adds a new block hash to the message. Up to
|
|
// MaxISBlocksAtHeadPerMsg may be added before this function errors out.
|
|
func (msg *MsgInitState) AddBlockHash(hash *chainhash.Hash) error {
|
|
const op = "MsgInitState.AddBlockHash"
|
|
if len(msg.BlockHashes)+1 > MaxISBlocksAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many block hashes for message [max %v]",
|
|
MaxISBlocksAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyHeaders, msg)
|
|
}
|
|
|
|
msg.BlockHashes = append(msg.BlockHashes, *hash)
|
|
return nil
|
|
}
|
|
|
|
// AddVoteHash adds a new vote hash to the message. Up to
|
|
// MaxISVotesAtHeadPerMsg may be added before this function errors out.
|
|
func (msg *MsgInitState) AddVoteHash(hash *chainhash.Hash) error {
|
|
const op = "MsgInitState.AddVoteHash"
|
|
if len(msg.VoteHashes)+1 > MaxISVotesAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many vote hashes for message [max %v]",
|
|
MaxISVotesAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyVotes, msg)
|
|
}
|
|
|
|
msg.VoteHashes = append(msg.VoteHashes, *hash)
|
|
return nil
|
|
}
|
|
|
|
// AddTSpendHash adds a new treasury spend hash to the message. Up to
|
|
// MaxISTSpendsAtHeadPerMsg may be added before this function errors out.
|
|
func (msg *MsgInitState) AddTSpendHash(hash *chainhash.Hash) error {
|
|
const op = "MsgInitState.AddTSpendHash"
|
|
if len(msg.TSpendHashes)+1 > MaxISTSpendsAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many tspend hashes for message [max %v]",
|
|
MaxISTSpendsAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyTSpends, msg)
|
|
}
|
|
|
|
msg.TSpendHashes = append(msg.TSpendHashes, *hash)
|
|
return nil
|
|
}
|
|
|
|
// BtcDecode decodes r using the protocol encoding into the receiver.
|
|
// This is part of the Message interface implementation.
|
|
func (msg *MsgInitState) BtcDecode(r io.Reader, pver uint32) error {
|
|
const op = "MsgInitState.BtcDecode"
|
|
if pver < InitStateVersion {
|
|
msg := fmt.Sprintf("%s message invalid for protocol version %d",
|
|
msg.Command(), pver)
|
|
return messageError(op, ErrMsgInvalidForPVer, msg)
|
|
}
|
|
|
|
// Read num block hashes and limit to max.
|
|
count, err := ReadVarInt(r, pver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > MaxISBlocksAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many block hashes for message "+
|
|
"[count %v, max %v]", count, MaxISBlocksAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyBlocks, msg)
|
|
}
|
|
|
|
msg.BlockHashes = make([]chainhash.Hash, count)
|
|
for i := uint64(0); i < count; i++ {
|
|
err := readElement(r, &msg.BlockHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Read num vote hashes and limit to max.
|
|
count, err = ReadVarInt(r, pver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > MaxISVotesAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many vote hashes for message "+
|
|
"[count %v, max %v]", count, MaxISVotesAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyVotes, msg)
|
|
}
|
|
|
|
msg.VoteHashes = make([]chainhash.Hash, count)
|
|
for i := uint64(0); i < count; i++ {
|
|
err := readElement(r, &msg.VoteHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Read num tspend hashes and limit to max.
|
|
count, err = ReadVarInt(r, pver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > MaxISTSpendsAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many tspend hashes for message "+
|
|
"[count %v, max %v]", count, MaxISTSpendsAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyTSpends, msg)
|
|
}
|
|
|
|
msg.TSpendHashes = make([]chainhash.Hash, count)
|
|
for i := uint64(0); i < count; i++ {
|
|
err := readElement(r, &msg.TSpendHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BtcEncode encodes the receiver to w using the protocol encoding.
|
|
// This is part of the Message interface implementation.
|
|
func (msg *MsgInitState) BtcEncode(w io.Writer, pver uint32) error {
|
|
const op = "MsgInitState.BtcEncode"
|
|
if pver < InitStateVersion {
|
|
msg := fmt.Sprintf("%s message invalid for protocol version %d",
|
|
msg.Command(), pver)
|
|
return messageError(op, ErrMsgInvalidForPVer, msg)
|
|
}
|
|
|
|
// Write block hashes.
|
|
count := len(msg.BlockHashes)
|
|
if count > MaxISBlocksAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many block hashes for message "+
|
|
"[count %v, max %v]", count, MaxISBlocksAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyBlocks, msg)
|
|
}
|
|
|
|
err := WriteVarInt(w, pver, uint64(count))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range msg.BlockHashes {
|
|
err = writeElement(w, &msg.BlockHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write vote hashes.
|
|
count = len(msg.VoteHashes)
|
|
if count > MaxISVotesAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many vote hashes for message "+
|
|
"[count %v, max %v]", count, MaxISVotesAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyVotes, msg)
|
|
}
|
|
|
|
err = WriteVarInt(w, pver, uint64(count))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range msg.VoteHashes {
|
|
err = writeElement(w, &msg.VoteHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write tspend hashes.
|
|
count = len(msg.TSpendHashes)
|
|
if count > MaxISTSpendsAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many tspend hashes for message "+
|
|
"[count %v, max %v]", count, MaxISTSpendsAtHeadPerMsg)
|
|
return messageError(op, ErrTooManyTSpends, msg)
|
|
}
|
|
|
|
err = WriteVarInt(w, pver, uint64(count))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range msg.TSpendHashes {
|
|
err = writeElement(w, &msg.TSpendHashes[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Command returns the protocol command string for the message. This is part
|
|
// of the Message interface implementation.
|
|
func (msg *MsgInitState) Command() string {
|
|
return CmdInitState
|
|
}
|
|
|
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
|
// receiver. This is part of the Message interface implementation.
|
|
func (msg *MsgInitState) MaxPayloadLength(pver uint32) uint32 {
|
|
if pver < InitStateVersion {
|
|
return 0
|
|
}
|
|
|
|
return uint32(VarIntSerializeSize(MaxISBlocksAtHeadPerMsg)) +
|
|
(MaxISBlocksAtHeadPerMsg * chainhash.HashSize) +
|
|
uint32(VarIntSerializeSize(MaxISVotesAtHeadPerMsg)) +
|
|
(MaxISVotesAtHeadPerMsg * chainhash.HashSize) +
|
|
uint32(VarIntSerializeSize(MaxISTSpendsAtHeadPerMsg)) +
|
|
(MaxISTSpendsAtHeadPerMsg * chainhash.HashSize)
|
|
}
|
|
|
|
// NewMsgInitState returns a new Decred initstate message that conforms to the
|
|
// Message interface using the defaults for the fields.
|
|
func NewMsgInitState() *MsgInitState {
|
|
return &MsgInitState{
|
|
BlockHashes: make([]chainhash.Hash, 0, MaxISBlocksAtHeadPerMsg),
|
|
VoteHashes: make([]chainhash.Hash, 0, MaxISVotesAtHeadPerMsg),
|
|
TSpendHashes: make([]chainhash.Hash, 0, MaxISTSpendsAtHeadPerMsg),
|
|
}
|
|
}
|
|
|
|
// NewMsgInitStateFilled returns a new Decred initstate message that conforms
|
|
// to the Message interface and fills the message with the provided data. This
|
|
// is useful in situations where the data slices are already built as it avoids
|
|
// performing a second allocation and data copy.
|
|
//
|
|
// The provided slices are checked for their maximum length.
|
|
func NewMsgInitStateFilled(blockHashes []chainhash.Hash, voteHashes []chainhash.Hash,
|
|
tspendHashes []chainhash.Hash) (*MsgInitState, error) {
|
|
const op = "NewMsgInitStateFilled"
|
|
|
|
count := len(blockHashes)
|
|
if count > MaxISBlocksAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many block hashes for message "+
|
|
"[count %v, max %v]", count, MaxISBlocksAtHeadPerMsg)
|
|
return nil, messageError(op, ErrTooManyBlocks, msg)
|
|
}
|
|
|
|
count = len(voteHashes)
|
|
if count > MaxISVotesAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many vote hashes for message "+
|
|
"[count %v, max %v]", count, MaxISVotesAtHeadPerMsg)
|
|
return nil, messageError(op, ErrTooManyVotes, msg)
|
|
}
|
|
|
|
count = len(tspendHashes)
|
|
if count > MaxISTSpendsAtHeadPerMsg {
|
|
msg := fmt.Sprintf("too many tspend hashes for message "+
|
|
"[count %v, max %v]", count, MaxISTSpendsAtHeadPerMsg)
|
|
return nil, messageError(op, ErrTooManyTSpends, msg)
|
|
}
|
|
|
|
return &MsgInitState{
|
|
BlockHashes: blockHashes,
|
|
VoteHashes: voteHashes,
|
|
TSpendHashes: tspendHashes,
|
|
}, nil
|
|
}
|