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 }