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 }