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  }