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