diff --git a/common.go b/common.go index f9582a94d..42204ec90 100644 --- a/common.go +++ b/common.go @@ -139,6 +139,15 @@ func readElement(r io.Reader, element interface{}) error { } *e = BloomUpdateType(b[0]) return nil + + case *RejectCode: + b := scratch[0:1] + _, err := io.ReadFull(r, b) + if err != nil { + return err + } + *e = RejectCode(b[0]) + return nil } // Fall back to the slower binary.Read if a fast path was not available @@ -280,6 +289,15 @@ func writeElement(w io.Writer, element interface{}) error { return err } return nil + + case RejectCode: + b := scratch[0:1] + b[0] = uint8(e) + _, err := w.Write(b) + if err != nil { + return err + } + return nil } // Fall back to the slower binary.Write if a fast path was not available diff --git a/message.go b/message.go index e007a594b..26ec0c2db 100644 --- a/message.go +++ b/message.go @@ -46,6 +46,7 @@ const ( cmdFilterClear = "filterclear" cmdFilterLoad = "filterload" cmdMerkleBlock = "merkleblock" + cmdReject = "reject" ) // Message is an interface that describes a bitcoin message. A type that @@ -124,6 +125,9 @@ func makeEmptyMessage(command string) (Message, error) { case cmdMerkleBlock: msg = &MsgMerkleBlock{} + case cmdReject: + msg = &MsgReject{} + default: return nil, fmt.Errorf("unhandled command [%s]", command) } diff --git a/msgreject.go b/msgreject.go new file mode 100644 index 000000000..14856cfbe --- /dev/null +++ b/msgreject.go @@ -0,0 +1,184 @@ +// Copyright (c) 2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "fmt" + "io" +) + +// RejectCode represents a numeric value by which a remote peer indicates +// why a message was rejected. +type RejectCode uint8 + +// These constants define the various supported reject codes. +const ( + RejectMalformed RejectCode = 0x01 + RejectInvalid RejectCode = 0x10 + RejectObsolete RejectCode = 0x11 + RejectDuplicate RejectCode = 0x12 + RejectNonstandard RejectCode = 0x40 + RejectDust RejectCode = 0x41 + RejectInsufficientFee RejectCode = 0x42 + RejectCheckpoint RejectCode = 0x43 +) + +// Map of reject codes back strings for pretty printing. +var rejectCodeStrings = map[RejectCode]string{ + RejectMalformed: "REJECT_MALFORMED", + RejectInvalid: "REJECT_INVALID", + RejectObsolete: "REJECT_OBSOLETE", + RejectDuplicate: "REJECT_DUPLICATE", + RejectNonstandard: "REJECT_NONSTANDARD", + RejectDust: "REJECT_DUST", + RejectInsufficientFee: "REJECT_INSUFFICIENTFEE", + RejectCheckpoint: "REJECT_CHECKPOINT", +} + +// String returns the RejectCode in human-readable form. +func (code RejectCode) String() string { + if s, ok := rejectCodeStrings[code]; ok { + return s + } + + return fmt.Sprintf("Unknown RejectCode (%d)", uint8(code)) +} + +// MsgReject implements the Message interface and represents a bitcoin reject +// message. +// +// This message was not added until protocol version RejectVersion. +type MsgReject struct { + // Cmd is the command for the message which was rejected such as + // as cmdBlock or cmdTx. This can be obtained from the Command function + // of a Message. + Cmd string + + // RejectCode is a code indicating why the command was rejected. It + // is encoded as a uint8 on the wire. + Code RejectCode + + // Reason is a human-readable string with specific details (over and + // above the reject code) about why the command was rejected. + Reason string + + // Hash identifies a specific block or transaction that was rejected + // and therefore only applies the MsgBlock and MsgTx messages. + Hash ShaHash +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +func (msg *MsgReject) BtcDecode(r io.Reader, pver uint32) error { + if pver < RejectVersion { + str := fmt.Sprintf("reject message invalid for protocol "+ + "version %d", pver) + return messageError("MsgReject.BtcDecode", str) + } + + // Command that was rejected. + cmd, err := readVarString(r, pver) + if err != nil { + return err + } + msg.Cmd = cmd + + // Code indicating why the command was rejected. + err = readElement(r, &msg.Code) + if err != nil { + return err + } + + // Human readable string with specific details (over and above the + // reject code above) about why the command was rejected. + reason, err := readVarString(r, pver) + if err != nil { + return err + } + msg.Reason = reason + + // cmdBlock and cmdTx messages have an additional hash field that + // identifies the specific block or transaction. + if msg.Cmd == cmdBlock || msg.Cmd == cmdTx { + err := readElement(r, &msg.Hash) + if err != nil { + return err + } + } + + return nil +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +func (msg *MsgReject) BtcEncode(w io.Writer, pver uint32) error { + if pver < RejectVersion { + str := fmt.Sprintf("reject message invalid for protocol "+ + "version %d", pver) + return messageError("MsgReject.BtcEncode", str) + } + + // Command that was rejected. + err := writeVarString(w, pver, msg.Cmd) + if err != nil { + return err + } + + // Code indicating why the command was rejected. + err = writeElement(w, msg.Code) + if err != nil { + return err + } + + // Human readable string with specific details (over and above the + // reject code above) about why the command was rejected. + err = writeVarString(w, pver, msg.Reason) + if err != nil { + return err + } + + // cmdBlock and cmdTx messages have an additional hash field that + // identifies the specific block or transaction. + if msg.Cmd == cmdBlock || msg.Cmd == cmdTx { + err := writeElement(w, &msg.Hash) + 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 *MsgReject) Command() string { + return cmdReject +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgReject) MaxPayloadLength(pver uint32) uint32 { + plen := uint32(0) + // The reject message did not exist before protocol version + // RejectVersion. + if pver >= RejectVersion { + // Unfortunately the bitcoin protocol does not enforce a sane + // limit on the length of the reason, so the max payload is the + // overall maximum message payload. + plen = maxMessagePayload + } + + return plen +} + +// NewMsgReject returns a new bitcoin reject message that conforms to the +// Message interface. See MsgReject for details. +func NewMsgReject(command string, code RejectCode, reason string) *MsgReject { + return &MsgReject{ + Cmd: command, + Code: code, + Reason: reason, + } +} diff --git a/protocol.go b/protocol.go index 7436c0b81..dbab3f23a 100644 --- a/protocol.go +++ b/protocol.go @@ -45,6 +45,10 @@ const ( // bloom filtering related messages and extended the version message // with a relay flag (pver >= BIP0037Version). BIP0037Version uint32 = 70001 + + // RejectVersion is the protocol version which added a new reject + // message. + RejectVersion uint32 = 70002 ) // ServiceFlag identifies services supported by a bitcoin peer.