github.com/onflow/flow-go@v0.33.17/network/message/message_scope.go (about) 1 package message 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/onflow/flow-go/crypto/hash" 8 "github.com/onflow/flow-go/model/flow" 9 "github.com/onflow/flow-go/network/channels" 10 ) 11 12 const ( 13 // eventIDPackingPrefix is used as a salt to generate payload hash for messages. 14 eventIDPackingPrefix = "libp2ppacking" 15 ) 16 17 // EventId computes the event ID for a given channel and payload (i.e., the hash of the payload and channel). 18 // All errors returned by this function are benign and should not cause the node to crash. 19 // It errors if the hash function fails to hash the payload and channel. 20 func EventId(channel channels.Channel, payload []byte) (hash.Hash, error) { 21 // use a hash with an engine-specific salt to get the payload hash 22 h := hash.NewSHA3_384() 23 _, err := h.Write([]byte(eventIDPackingPrefix + channel)) 24 if err != nil { 25 return nil, fmt.Errorf("could not hash channel as salt: %w", err) 26 } 27 28 _, err = h.Write(payload) 29 if err != nil { 30 return nil, fmt.Errorf("could not hash event: %w", err) 31 } 32 33 return h.SumHash(), nil 34 } 35 36 // MessageType returns the type of the message payload. 37 func MessageType(decodedPayload interface{}) string { 38 return strings.TrimLeft(fmt.Sprintf("%T", decodedPayload), "*") 39 } 40 41 // IncomingMessageScope captures the context around an incoming message that is received by the network layer. 42 type IncomingMessageScope struct { 43 originId flow.Identifier // the origin node ID. 44 targetIds flow.IdentifierList // the target node IDs (i.e., intended recipients). 45 eventId hash.Hash // hash of the payload and channel. 46 msg *Message // the raw message received. 47 decodedPayload interface{} // decoded payload of the message. 48 protocol ProtocolType // the type of protocol used to receive the message. 49 } 50 51 // NewIncomingScope creates a new incoming message scope. 52 // All errors returned by this function are benign and should not cause the node to crash, especially that it is not 53 // safe to crash the node when receiving a message. 54 // It errors if event id (i.e., hash of the payload and channel) cannot be computed, or if it fails to 55 // convert the target IDs from bytes slice to a flow.IdentifierList. 56 func NewIncomingScope(originId flow.Identifier, protocol ProtocolType, msg *Message, decodedPayload interface{}) (*IncomingMessageScope, error) { 57 eventId, err := EventId(channels.Channel(msg.ChannelID), msg.Payload) 58 if err != nil { 59 return nil, fmt.Errorf("could not compute event id: %w", err) 60 } 61 62 targetIds, err := flow.ByteSlicesToIds(msg.TargetIDs) 63 if err != nil { 64 return nil, fmt.Errorf("could not convert target ids: %w", err) 65 } 66 return &IncomingMessageScope{ 67 eventId: eventId, 68 originId: originId, 69 msg: msg, 70 decodedPayload: decodedPayload, 71 protocol: protocol, 72 targetIds: targetIds, 73 }, nil 74 } 75 76 func (m IncomingMessageScope) OriginId() flow.Identifier { 77 return m.originId 78 } 79 80 func (m IncomingMessageScope) Proto() *Message { 81 return m.msg 82 } 83 84 func (m IncomingMessageScope) DecodedPayload() interface{} { 85 return m.decodedPayload 86 } 87 88 func (m IncomingMessageScope) Protocol() ProtocolType { 89 return m.protocol 90 } 91 92 func (m IncomingMessageScope) Channel() channels.Channel { 93 return channels.Channel(m.msg.ChannelID) 94 } 95 96 func (m IncomingMessageScope) Size() int { 97 return m.msg.Size() 98 } 99 100 func (m IncomingMessageScope) TargetIDs() flow.IdentifierList { 101 return m.targetIds 102 } 103 104 func (m IncomingMessageScope) EventID() []byte { 105 return m.eventId[:] 106 } 107 108 func (m IncomingMessageScope) PayloadType() string { 109 return MessageType(m.decodedPayload) 110 } 111 112 // OutgoingMessageScope captures the context around an outgoing message that is about to be sent. 113 type OutgoingMessageScope struct { 114 targetIds flow.IdentifierList // the target node IDs. 115 topic channels.Topic // the topic, i.e., channel-id/spork-id. 116 payload interface{} // the payload to be sent. 117 encoder func(interface{}) ([]byte, error) // the encoder to encode the payload. 118 msg *Message // raw proto message sent on wire. 119 protocol ProtocolType // the type of protocol used to send the message. 120 } 121 122 // NewOutgoingScope creates a new outgoing message scope. 123 // All errors returned by this function are benign and should not cause the node to crash. 124 // It errors if the encoder fails to encode the payload into a protobuf message, or 125 // if the number of target IDs does not match the protocol type (i.e., unicast messages 126 // should have exactly one target ID, while pubsub messages should have at least one target ID). 127 func NewOutgoingScope( 128 targetIds flow.IdentifierList, 129 topic channels.Topic, 130 payload interface{}, 131 encoder func(interface{}) ([]byte, error), 132 protocolType ProtocolType) (*OutgoingMessageScope, error) { 133 scope := &OutgoingMessageScope{ 134 targetIds: targetIds, 135 topic: topic, 136 payload: payload, 137 encoder: encoder, 138 protocol: protocolType, 139 } 140 141 if protocolType == ProtocolTypeUnicast { 142 // for unicast messages, we should have exactly one target. 143 if len(targetIds) != 1 { 144 return nil, fmt.Errorf("expected exactly one target id for unicast message, got: %d", len(targetIds)) 145 } 146 } 147 if protocolType == ProtocolTypePubSub { 148 // for pubsub messages, we should have at least one target. 149 if len(targetIds) == 0 { 150 return nil, fmt.Errorf("expected at least one target id for pubsub message, got: %d", len(targetIds)) 151 } 152 } 153 154 msg, err := scope.buildMessage() 155 if err != nil { 156 return nil, fmt.Errorf("could not build message: %w", err) 157 } 158 scope.msg = msg 159 return scope, nil 160 } 161 162 func (o OutgoingMessageScope) TargetIds() flow.IdentifierList { 163 return o.targetIds 164 } 165 166 func (o OutgoingMessageScope) Size() int { 167 return o.msg.Size() 168 } 169 170 func (o OutgoingMessageScope) PayloadType() string { 171 return MessageType(o.payload) 172 } 173 174 func (o OutgoingMessageScope) Topic() channels.Topic { 175 return o.topic 176 } 177 178 // buildMessage builds the raw proto message to be sent on the wire. 179 func (o OutgoingMessageScope) buildMessage() (*Message, error) { 180 payload, err := o.encoder(o.payload) 181 if err != nil { 182 return nil, fmt.Errorf("could not encode payload: %w", err) 183 } 184 185 emTargets := make([][]byte, 0) 186 for _, targetId := range o.targetIds { 187 tempID := targetId // avoid capturing loop variable 188 emTargets = append(emTargets, tempID[:]) 189 } 190 191 channel, ok := channels.ChannelFromTopic(o.topic) 192 if !ok { 193 return nil, fmt.Errorf("could not convert topic to channel: %s", o.topic) 194 } 195 196 return &Message{ 197 TargetIDs: emTargets, 198 ChannelID: channel.String(), 199 Payload: payload, 200 }, nil 201 } 202 203 func (o OutgoingMessageScope) Proto() *Message { 204 return o.msg 205 }