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  }