lbcd/wire/msgversion.go
Dave Collins 5de5b7354c wire: Avoid allocation on timestamp decodes.
Since the protocol encodes timestamps differently depending on the
message, the code currently decodes into a local variable and then
converts it to a time.Time.  However, this causes an allocation due to
the local having to escape to the heap in order for the readElement
function to write to it.

So, in order to avoid that, this introduces two new types for a
timestamp named uint32Time and int64Time that are encoded as the
respective type on the read.  When calling the readElements function,
the time.Time field in the message is cast to a pointer of the
appropriate type which effectively allows the allocations to be avoided.

The following is a before and after comparison of the allocations
with the benchmarks that did not change removed:

benchmark              old allocs     new allocs     delta
----------------------------------------------------------------------
ReadBlockHeader        1              0              -100.00%
DecodeHeaders          4001           2001           -49.99%
DecodeAddr             4001           3001           -24.99%
DecodeMerkleBlock      108            107            -0.93%
2016-06-03 17:08:31 -05:00

292 lines
8.4 KiB
Go

// Copyright (c) 2013-2016 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"
"net"
"strings"
"time"
)
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
// version message (MsgVersion).
const MaxUserAgentLen = 2000
// DefaultUserAgent for wire in the stack
const DefaultUserAgent = "/btcwire:0.4.0/"
// MsgVersion implements the Message interface and represents a bitcoin version
// message. It is used for a peer to advertise itself as soon as an outbound
// connection is made. The remote peer then uses this information along with
// its own to negotiate. The remote peer must then respond with a version
// message of its own containing the negotiated values followed by a verack
// message (MsgVerAck). This exchange must take place before any further
// communication is allowed to proceed.
type MsgVersion struct {
// Version of the protocol the node is using.
ProtocolVersion int32
// Bitfield which identifies the enabled services.
Services ServiceFlag
// Time the message was generated. This is encoded as an int64 on the wire.
Timestamp time.Time
// Address of the remote peer.
AddrYou NetAddress
// Address of the local peer.
AddrMe NetAddress
// Unique value associated with message that is used to detect self
// connections.
Nonce uint64
// The user agent that generated messsage. This is a encoded as a varString
// on the wire. This has a max length of MaxUserAgentLen.
UserAgent string
// Last block seen by the generator of the version message.
LastBlock int32
// Don't announce transactions to peer.
DisableRelayTx bool
}
// HasService returns whether the specified service is supported by the peer
// that generated the message.
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
return msg.Services&service == service
}
// AddService adds service as a supported service by the peer generating the
// message.
func (msg *MsgVersion) AddService(service ServiceFlag) {
msg.Services |= service
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// The version message is special in that the protocol version hasn't been
// negotiated yet. As a result, the pver field is ignored and any fields which
// are added in new versions are optional. This also mean that r must be a
// *bytes.Buffer so the number of remaining bytes can be ascertained.
//
// This is part of the Message interface implementation.
func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error {
buf, ok := r.(*bytes.Buffer)
if !ok {
return fmt.Errorf("MsgVersion.BtcDecode reader is not a " +
"*bytes.Buffer")
}
err := readElements(buf, &msg.ProtocolVersion, &msg.Services,
(*int64Time)(&msg.Timestamp))
if err != nil {
return err
}
err = readNetAddress(buf, pver, &msg.AddrYou, false)
if err != nil {
return err
}
// Protocol versions >= 106 added a from address, nonce, and user agent
// field and they are only considered present if there are bytes
// remaining in the message.
if buf.Len() > 0 {
err = readNetAddress(buf, pver, &msg.AddrMe, false)
if err != nil {
return err
}
}
if buf.Len() > 0 {
err = readElement(buf, &msg.Nonce)
if err != nil {
return err
}
}
if buf.Len() > 0 {
userAgent, err := ReadVarString(buf, pver)
if err != nil {
return err
}
err = validateUserAgent(userAgent)
if err != nil {
return err
}
msg.UserAgent = userAgent
}
// Protocol versions >= 209 added a last known block field. It is only
// considered present if there are bytes remaining in the message.
if buf.Len() > 0 {
err = readElement(buf, &msg.LastBlock)
if err != nil {
return err
}
}
// There was no relay transactions field before BIP0037Version, but
// the default behavior prior to the addition of the field was to always
// relay transactions.
if buf.Len() > 0 {
// It's safe to ignore the error here since the buffer has at
// least one byte and that byte will result in a boolean value
// regardless of its value. Also, the wire encoding for the
// field is true when transactions should be relayed, so reverse
// it for the DisableRelayTx field.
var relayTx bool
readElement(r, &relayTx)
msg.DisableRelayTx = !relayTx
}
return nil
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
err := validateUserAgent(msg.UserAgent)
if err != nil {
return err
}
err = writeElements(w, msg.ProtocolVersion, msg.Services,
msg.Timestamp.Unix())
if err != nil {
return err
}
err = writeNetAddress(w, pver, &msg.AddrYou, false)
if err != nil {
return err
}
err = writeNetAddress(w, pver, &msg.AddrMe, false)
if err != nil {
return err
}
err = writeElement(w, msg.Nonce)
if err != nil {
return err
}
err = WriteVarString(w, pver, msg.UserAgent)
if err != nil {
return err
}
err = writeElement(w, msg.LastBlock)
if err != nil {
return err
}
// There was no relay transactions field before BIP0037Version. Also,
// the wire encoding for the field is true when transactions should be
// relayed, so reverse it from the DisableRelayTx field.
if pver >= BIP0037Version {
err = writeElement(w, !msg.DisableRelayTx)
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 *MsgVersion) Command() string {
return CmdVersion
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 {
// XXX: <= 106 different
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes +
// remote and local net addresses + nonce 8 bytes + length of user
// agent (varInt) + max allowed useragent length + last block 4 bytes +
// relay transactions flag 1 byte.
return 33 + (maxNetAddressPayload(pver) * 2) + MaxVarIntPayload +
MaxUserAgentLen
}
// NewMsgVersion returns a new bitcoin version message that conforms to the
// Message interface using the passed parameters and defaults for the remaining
// fields.
func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64,
lastBlock int32) *MsgVersion {
// Limit the timestamp to one second precision since the protocol
// doesn't support better.
return &MsgVersion{
ProtocolVersion: int32(ProtocolVersion),
Services: 0,
Timestamp: time.Unix(time.Now().Unix(), 0),
AddrYou: *you,
AddrMe: *me,
Nonce: nonce,
UserAgent: DefaultUserAgent,
LastBlock: lastBlock,
DisableRelayTx: false,
}
}
// NewMsgVersionFromConn is a convenience function that extracts the remote
// and local address from conn and returns a new bitcoin version message that
// conforms to the Message interface. See NewMsgVersion.
func NewMsgVersionFromConn(conn net.Conn, nonce uint64,
lastBlock int32) (*MsgVersion, error) {
// Don't assume any services until we know otherwise.
lna, err := NewNetAddress(conn.LocalAddr(), 0)
if err != nil {
return nil, err
}
// Don't assume any services until we know otherwise.
rna, err := NewNetAddress(conn.RemoteAddr(), 0)
if err != nil {
return nil, err
}
return NewMsgVersion(lna, rna, nonce, lastBlock), nil
}
// validateUserAgent checks userAgent length against MaxUserAgentLen
func validateUserAgent(userAgent string) error {
if len(userAgent) > MaxUserAgentLen {
str := fmt.Sprintf("user agent too long [len %v, max %v]",
len(userAgent), MaxUserAgentLen)
return messageError("MsgVersion", str)
}
return nil
}
// AddUserAgent adds a user agent to the user agent string for the version
// message. The version string is not defined to any strict format, although
// it is recommended to use the form "major.minor.revision" e.g. "2.6.41".
func (msg *MsgVersion) AddUserAgent(name string, version string,
comments ...string) error {
newUserAgent := fmt.Sprintf("%s:%s", name, version)
if len(comments) != 0 {
newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent,
strings.Join(comments, "; "))
}
newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent)
err := validateUserAgent(newUserAgent)
if err != nil {
return err
}
msg.UserAgent = newUserAgent
return nil
}