github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/network/message.go (about)

     1  package network
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/core/block"
     8  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
     9  	"github.com/nspcc-dev/neo-go/pkg/io"
    10  	"github.com/nspcc-dev/neo-go/pkg/network/payload"
    11  )
    12  
    13  //go:generate stringer -type=CommandType -output=message_string.go
    14  
    15  // CompressionMinSize is the lower bound to apply compression.
    16  const CompressionMinSize = 1024
    17  
    18  // Message is a complete message sent between nodes.
    19  type Message struct {
    20  	// Flags that represents whether a message is compressed.
    21  	// 0 for None, 1 for Compressed.
    22  	Flags MessageFlag
    23  	// Command is a byte command code.
    24  	Command CommandType
    25  
    26  	// Payload send with the message.
    27  	Payload payload.Payload
    28  
    29  	// Compressed message payload.
    30  	compressedPayload []byte
    31  
    32  	// StateRootInHeader specifies if the state root is included in the block header.
    33  	// This is needed for correct decoding.
    34  	StateRootInHeader bool
    35  }
    36  
    37  // MessageFlag represents compression level of a message payload.
    38  type MessageFlag byte
    39  
    40  // Possible message flags.
    41  const (
    42  	Compressed MessageFlag = 1 << iota
    43  	None       MessageFlag = 0
    44  )
    45  
    46  // CommandType represents the type of a message command.
    47  type CommandType byte
    48  
    49  // Valid protocol commands used to send between nodes.
    50  const (
    51  	// Handshaking.
    52  	CMDVersion CommandType = 0x00
    53  	CMDVerack  CommandType = 0x01
    54  
    55  	// Connectivity.
    56  	CMDGetAddr CommandType = 0x10
    57  	CMDAddr    CommandType = 0x11
    58  	CMDPing    CommandType = 0x18
    59  	CMDPong    CommandType = 0x19
    60  
    61  	// Synchronization.
    62  	CMDGetHeaders       CommandType = 0x20
    63  	CMDHeaders          CommandType = 0x21
    64  	CMDGetBlocks        CommandType = 0x24
    65  	CMDMempool          CommandType = 0x25
    66  	CMDInv              CommandType = 0x27
    67  	CMDGetData          CommandType = 0x28
    68  	CMDGetBlockByIndex  CommandType = 0x29
    69  	CMDNotFound         CommandType = 0x2a
    70  	CMDTX                           = CommandType(payload.TXType)
    71  	CMDBlock                        = CommandType(payload.BlockType)
    72  	CMDExtensible                   = CommandType(payload.ExtensibleType)
    73  	CMDP2PNotaryRequest             = CommandType(payload.P2PNotaryRequestType)
    74  	CMDGetMPTData       CommandType = 0x51 // 0x5.. commands are used for extensions (P2PNotary, state exchange cmds)
    75  	CMDMPTData          CommandType = 0x52
    76  	CMDReject           CommandType = 0x2f
    77  
    78  	// SPV protocol.
    79  	CMDFilterLoad  CommandType = 0x30
    80  	CMDFilterAdd   CommandType = 0x31
    81  	CMDFilterClear CommandType = 0x32
    82  	CMDMerkleBlock CommandType = 0x38
    83  
    84  	// Others.
    85  	CMDAlert CommandType = 0x40
    86  )
    87  
    88  // NewMessage returns a new message with the given payload.
    89  func NewMessage(cmd CommandType, p payload.Payload) *Message {
    90  	return &Message{
    91  		Command: cmd,
    92  		Payload: p,
    93  		Flags:   None,
    94  	}
    95  }
    96  
    97  // Decode decodes a Message from the given reader.
    98  func (m *Message) Decode(br *io.BinReader) error {
    99  	m.Flags = MessageFlag(br.ReadB())
   100  	m.Command = CommandType(br.ReadB())
   101  	l := br.ReadVarUint()
   102  	// check the length first in order not to allocate memory
   103  	// for an empty compressed payload
   104  	if l == 0 {
   105  		switch m.Command {
   106  		case CMDFilterClear, CMDGetAddr, CMDMempool, CMDVerack:
   107  			m.Payload = payload.NewNullPayload()
   108  		default:
   109  			return fmt.Errorf("unexpected empty payload: %s", m.Command)
   110  		}
   111  		return nil
   112  	}
   113  	if l > payload.MaxSize {
   114  		return errors.New("invalid payload size")
   115  	}
   116  	m.compressedPayload = make([]byte, l)
   117  	br.ReadBytes(m.compressedPayload)
   118  	if br.Err != nil {
   119  		return br.Err
   120  	}
   121  	return m.decodePayload()
   122  }
   123  
   124  func (m *Message) decodePayload() error {
   125  	buf := m.compressedPayload
   126  	// try decompression
   127  	if m.Flags&Compressed != 0 {
   128  		d, err := decompress(m.compressedPayload)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		buf = d
   133  	}
   134  
   135  	var p payload.Payload
   136  	switch m.Command {
   137  	case CMDVersion:
   138  		p = &payload.Version{}
   139  	case CMDInv, CMDGetData:
   140  		p = &payload.Inventory{}
   141  	case CMDGetMPTData:
   142  		p = &payload.MPTInventory{}
   143  	case CMDMPTData:
   144  		p = &payload.MPTData{}
   145  	case CMDAddr:
   146  		p = &payload.AddressList{}
   147  	case CMDBlock:
   148  		p = block.New(m.StateRootInHeader)
   149  	case CMDExtensible:
   150  		p = payload.NewExtensible()
   151  	case CMDP2PNotaryRequest:
   152  		p = &payload.P2PNotaryRequest{}
   153  	case CMDGetBlocks:
   154  		p = &payload.GetBlocks{}
   155  	case CMDGetHeaders:
   156  		fallthrough
   157  	case CMDGetBlockByIndex:
   158  		p = &payload.GetBlockByIndex{}
   159  	case CMDHeaders:
   160  		p = &payload.Headers{StateRootInHeader: m.StateRootInHeader}
   161  	case CMDTX:
   162  		p, err := transaction.NewTransactionFromBytes(buf)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		m.Payload = p
   167  		return nil
   168  	case CMDMerkleBlock:
   169  		p = &payload.MerkleBlock{}
   170  	case CMDPing, CMDPong:
   171  		p = &payload.Ping{}
   172  	case CMDNotFound:
   173  		p = &payload.Inventory{}
   174  	default:
   175  		return fmt.Errorf("can't decode command %s", m.Command.String())
   176  	}
   177  	r := io.NewBinReaderFromBuf(buf)
   178  	p.DecodeBinary(r)
   179  	if r.Err == nil || errors.Is(r.Err, payload.ErrTooManyHeaders) {
   180  		m.Payload = p
   181  	}
   182  
   183  	return r.Err
   184  }
   185  
   186  // Encode encodes a Message to any given BinWriter.
   187  func (m *Message) Encode(br *io.BinWriter) error {
   188  	if err := m.tryCompressPayload(); err != nil {
   189  		return err
   190  	}
   191  	growSize := 2 + 1 // header + empty payload
   192  	if m.compressedPayload != nil {
   193  		growSize += 8 + len(m.compressedPayload) // varint + byte-slice
   194  	}
   195  	br.Grow(growSize)
   196  	br.WriteB(byte(m.Flags))
   197  	br.WriteB(byte(m.Command))
   198  	if m.compressedPayload != nil {
   199  		br.WriteVarBytes(m.compressedPayload)
   200  	} else {
   201  		br.WriteB(0)
   202  	}
   203  	return br.Err
   204  }
   205  
   206  // Bytes serializes a Message into the new allocated buffer and returns it.
   207  func (m *Message) Bytes() ([]byte, error) {
   208  	w := io.NewBufBinWriter()
   209  	if err := m.Encode(w.BinWriter); err != nil {
   210  		return nil, err
   211  	}
   212  	return w.Bytes(), nil
   213  }
   214  
   215  // tryCompressPayload sets the message's compressed payload to a serialized
   216  // payload and compresses it in case its size exceeds CompressionMinSize.
   217  func (m *Message) tryCompressPayload() error {
   218  	if m.Payload == nil {
   219  		return nil
   220  	}
   221  	buf := io.NewBufBinWriter()
   222  	m.Payload.EncodeBinary(buf.BinWriter)
   223  	if buf.Err != nil {
   224  		return buf.Err
   225  	}
   226  	compressedPayload := buf.Bytes()
   227  	if m.Flags&Compressed == 0 {
   228  		switch m.Payload.(type) {
   229  		case *payload.Headers, *payload.MerkleBlock, payload.NullPayload,
   230  			*payload.Inventory, *payload.MPTInventory:
   231  			break
   232  		default:
   233  			size := len(compressedPayload)
   234  			// try compression
   235  			if size > CompressionMinSize {
   236  				c, err := compress(compressedPayload)
   237  				if err == nil {
   238  					compressedPayload = c
   239  					m.Flags |= Compressed
   240  				} else {
   241  					return err
   242  				}
   243  			}
   244  		}
   245  	}
   246  	m.compressedPayload = compressedPayload
   247  	return nil
   248  }