github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/validator/pubsub/topic_validator.go (about)

     1  package validator
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	pubsub "github.com/libp2p/go-libp2p-pubsub"
     8  	"github.com/libp2p/go-libp2p/core/crypto"
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/network/channels"
    13  	"github.com/onflow/flow-go/network/message"
    14  	"github.com/onflow/flow-go/network/p2p"
    15  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    16  	"github.com/onflow/flow-go/network/validator"
    17  	_ "github.com/onflow/flow-go/utils/binstat"
    18  	"github.com/onflow/flow-go/utils/logging"
    19  )
    20  
    21  // messagePubKey extracts the public key of the envelope signer from a libp2p message.
    22  // The location of that key depends on the type of the key, see:
    23  // https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md
    24  // This reproduces the exact logic of the private function doing the same decoding in libp2p:
    25  // https://github.com/libp2p/go-libp2p-pubsub/blob/ba28f8ecfc551d4d916beb748d3384951bce3ed0/sign.go#L77
    26  func messageSigningID(m *pubsub.Message) (peer.ID, error) {
    27  	var pubk crypto.PubKey
    28  
    29  	// m.From is the original sender of the message (versus `m.ReceivedFrom` which is the last hop which sent us this message)
    30  	pid, err := peer.IDFromBytes(m.From)
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  
    35  	if m.Key == nil {
    36  		// no attached key, it must be extractable from the source ID
    37  		pubk, err = pid.ExtractPublicKey()
    38  		if err != nil {
    39  			return "", fmt.Errorf("cannot extract signing key: %s", err.Error())
    40  		}
    41  		if pubk == nil {
    42  			return "", fmt.Errorf("cannot extract signing key")
    43  		}
    44  	} else {
    45  		pubk, err = crypto.UnmarshalPublicKey(m.Key)
    46  		if err != nil {
    47  			return "", fmt.Errorf("cannot unmarshal signing key: %s", err.Error())
    48  		}
    49  
    50  		// verify that the source ID matches the attached key
    51  		if !pid.MatchesPublicKey(pubk) {
    52  			return "", fmt.Errorf("bad signing key; source ID %s doesn't match key", pid)
    53  		}
    54  	}
    55  
    56  	// the pid either contains or matches the signing pubKey
    57  	return pid, nil
    58  }
    59  
    60  // TopicValidatorData includes information about the message being sent.
    61  type TopicValidatorData struct {
    62  	Message *message.Message
    63  	From    peer.ID
    64  }
    65  
    66  // TopicValidator is the topic validator that is registered with libP2P whenever a flow libP2P node subscribes to a topic.
    67  // The TopicValidator will perform validation on the raw pubsub message.
    68  func TopicValidator(log zerolog.Logger, peerFilter func(peer.ID) error, validators ...validator.PubSubMessageValidator) p2p.TopicValidatorFunc {
    69  	log = log.With().
    70  		Str("component", "libp2p-node-topic-validator").
    71  		Logger()
    72  
    73  	return func(ctx context.Context, receivedFrom peer.ID, rawMsg *pubsub.Message) p2p.ValidationResult {
    74  		var msg message.Message
    75  		// convert the incoming raw message payload to Message type
    76  		// bs := binstat.EnterTimeVal(binstat.BinNet+":wire>1protobuf2message", int64(len(rawMsg.Data)))
    77  		err := msg.Unmarshal(rawMsg.Data)
    78  		// binstat.Leave(bs)
    79  		if err != nil {
    80  			return p2p.ValidationReject
    81  		}
    82  
    83  		from, err := messageSigningID(rawMsg)
    84  		if err != nil {
    85  			return p2p.ValidationReject
    86  		}
    87  
    88  		lg := log.With().
    89  			Str("peer_id", p2plogging.PeerId(from)).
    90  			Str("topic", rawMsg.GetTopic()).
    91  			Int("raw_msg_size", len(rawMsg.Data)).
    92  			Int("msg_size", msg.Size()).
    93  			Logger()
    94  
    95  		// verify sender is a known peer
    96  		if err := peerFilter(from); err != nil {
    97  			lg.Warn().
    98  				Err(err).
    99  				Bool(logging.KeySuspicious, true).
   100  				Msg("filtering message from un-allowed peer")
   101  			return p2p.ValidationReject
   102  		}
   103  
   104  		// verify ChannelID in message matches the topic over which the message was received
   105  		topic := channels.Topic(rawMsg.GetTopic())
   106  		actualChannel, ok := channels.ChannelFromTopic(topic)
   107  		if !ok {
   108  			lg.Warn().
   109  				Bool(logging.KeySuspicious, true).
   110  				Msg("could not convert topic to channel")
   111  			return p2p.ValidationReject
   112  		}
   113  
   114  		lg = lg.With().Str("channel", msg.ChannelID).Logger()
   115  
   116  		channel := channels.Channel(msg.ChannelID)
   117  		if channel != actualChannel {
   118  			log.Warn().
   119  				Str("actual_channel", actualChannel.String()).
   120  				Bool(logging.KeySuspicious, true).
   121  				Msg("channel id in message does not match pubsub topic")
   122  			return p2p.ValidationReject
   123  		}
   124  
   125  		rawMsg.ValidatorData = TopicValidatorData{
   126  			Message: &msg,
   127  			From:    from,
   128  		}
   129  
   130  		result := p2p.ValidationAccept
   131  		for _, v := range validators {
   132  			switch res := v(from, &msg); res {
   133  			case p2p.ValidationReject:
   134  				return res
   135  			case p2p.ValidationIgnore:
   136  				result = res
   137  			}
   138  		}
   139  
   140  		return result
   141  	}
   142  }