github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/pubsub.go (about) 1 package p2p 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/libp2p/go-libp2p-core/peer" 8 pubsub "github.com/libp2p/go-libp2p-pubsub" 9 pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" 10 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder" 11 "github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags" 12 pbrpc "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" 13 "github.com/prysmaticlabs/prysm/shared/featureconfig" 14 "github.com/prysmaticlabs/prysm/shared/hashutil" 15 "github.com/prysmaticlabs/prysm/shared/params" 16 ) 17 18 const ( 19 // overlay parameters 20 gossipSubD = 8 // topic stable mesh target count 21 gossipSubDlo = 6 // topic stable mesh low watermark 22 gossipSubDhi = 12 // topic stable mesh high watermark 23 24 // gossip parameters 25 gossipSubMcacheLen = 6 // number of windows to retain full messages in cache for `IWANT` responses 26 gossipSubMcacheGossip = 3 // number of windows to gossip about 27 gossipSubSeenTTL = 550 // number of heartbeat intervals to retain message IDs 28 29 // fanout ttl 30 gossipSubFanoutTTL = 60000000000 // TTL for fanout maps for topics we are not subscribed to but have published to, in nano seconds 31 32 // heartbeat interval 33 gossipSubHeartbeatInterval = 700 * time.Millisecond // frequency of heartbeat, milliseconds 34 35 // misc 36 randomSubD = 6 // random gossip target 37 ) 38 39 // JoinTopic will join PubSub topic, if not already joined. 40 func (s *Service) JoinTopic(topic string, opts ...pubsub.TopicOpt) (*pubsub.Topic, error) { 41 s.joinedTopicsLock.Lock() 42 defer s.joinedTopicsLock.Unlock() 43 44 if _, ok := s.joinedTopics[topic]; !ok { 45 topicHandle, err := s.pubsub.Join(topic, opts...) 46 if err != nil { 47 return nil, err 48 } 49 s.joinedTopics[topic] = topicHandle 50 } 51 52 return s.joinedTopics[topic], nil 53 } 54 55 // LeaveTopic closes topic and removes corresponding handler from list of joined topics. 56 // This method will return error if there are outstanding event handlers or subscriptions. 57 func (s *Service) LeaveTopic(topic string) error { 58 s.joinedTopicsLock.Lock() 59 defer s.joinedTopicsLock.Unlock() 60 61 if t, ok := s.joinedTopics[topic]; ok { 62 if err := t.Close(); err != nil { 63 return err 64 } 65 delete(s.joinedTopics, topic) 66 } 67 return nil 68 } 69 70 // PublishToTopic joins (if necessary) and publishes a message to a PubSub topic. 71 func (s *Service) PublishToTopic(ctx context.Context, topic string, data []byte, opts ...pubsub.PubOpt) error { 72 topicHandle, err := s.JoinTopic(topic) 73 if err != nil { 74 return err 75 } 76 77 // Wait for at least 1 peer to be available to receive the published message. 78 for { 79 if len(topicHandle.ListPeers()) > 0 || flags.Get().MinimumSyncPeers == 0 { 80 return topicHandle.Publish(ctx, data, opts...) 81 } 82 select { 83 case <-ctx.Done(): 84 return ctx.Err() 85 default: 86 time.Sleep(100 * time.Millisecond) 87 } 88 } 89 } 90 91 // SubscribeToTopic joins (if necessary) and subscribes to PubSub topic. 92 func (s *Service) SubscribeToTopic(topic string, opts ...pubsub.SubOpt) (*pubsub.Subscription, error) { 93 s.awaitStateInitialized() // Genesis time and genesis validator root are required to subscribe. 94 95 topicHandle, err := s.JoinTopic(topic) 96 if err != nil { 97 return nil, err 98 } 99 scoringParams, err := s.topicScoreParams(topic) 100 if err != nil { 101 return nil, err 102 } 103 104 if scoringParams != nil { 105 if err := topicHandle.SetScoreParams(scoringParams); err != nil { 106 return nil, err 107 } 108 logGossipParameters(topic, scoringParams) 109 } 110 return topicHandle.Subscribe(opts...) 111 } 112 113 // peerInspector will scrape all the relevant scoring data and add it to our 114 // peer handler. 115 func (s *Service) peerInspector(peerMap map[peer.ID]*pubsub.PeerScoreSnapshot) { 116 // Iterate through all the connected peers and through any of their 117 // relevant topics. 118 for pid, snap := range peerMap { 119 s.peers.Scorers().GossipScorer().SetGossipData(pid, snap.Score, 120 snap.BehaviourPenalty, convertTopicScores(snap.Topics)) 121 } 122 } 123 124 // Content addressable ID function. 125 // 126 // Ethereum Beacon Chain spec defines the message ID as: 127 // The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data: 128 // If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of 129 // the concatenation of `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data, 130 // i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`. 131 // 132 // Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of 133 // the concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data, 134 // i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]`. 135 func msgIDFunction(pmsg *pubsub_pb.Message) string { 136 decodedData, err := encoder.DecodeSnappy(pmsg.Data, params.BeaconNetworkConfig().GossipMaxSize) 137 if err != nil { 138 combinedData := append(params.BeaconNetworkConfig().MessageDomainInvalidSnappy[:], pmsg.Data...) 139 h := hashutil.Hash(combinedData) 140 return string(h[:20]) 141 } 142 combinedData := append(params.BeaconNetworkConfig().MessageDomainValidSnappy[:], decodedData...) 143 h := hashutil.Hash(combinedData) 144 return string(h[:20]) 145 } 146 147 func setPubSubParameters() { 148 pubsub.GossipSubDlo = gossipSubDlo 149 pubsub.GossipSubD = gossipSubD 150 pubsub.GossipSubHeartbeatInterval = gossipSubHeartbeatInterval 151 pubsub.GossipSubHistoryLength = gossipSubMcacheLen 152 pubsub.GossipSubHistoryGossip = gossipSubMcacheGossip 153 pubsub.TimeCacheDuration = 550 * gossipSubHeartbeatInterval 154 155 // Set a larger gossip history to ensure that slower 156 // messages have a longer time to be propagated. This 157 // comes with the tradeoff of larger memory usage and 158 // size of the seen message cache. 159 if featureconfig.Get().EnableLargerGossipHistory { 160 pubsub.GossipSubHistoryLength = 12 161 pubsub.GossipSubHistoryLength = 5 162 } 163 } 164 165 // convert from libp2p's internal schema to a compatible prysm protobuf format. 166 func convertTopicScores(topicMap map[string]*pubsub.TopicScoreSnapshot) map[string]*pbrpc.TopicScoreSnapshot { 167 newMap := make(map[string]*pbrpc.TopicScoreSnapshot, len(topicMap)) 168 for t, s := range topicMap { 169 newMap[t] = &pbrpc.TopicScoreSnapshot{ 170 TimeInMesh: uint64(s.TimeInMesh.Milliseconds()), 171 FirstMessageDeliveries: float32(s.FirstMessageDeliveries), 172 MeshMessageDeliveries: float32(s.MeshMessageDeliveries), 173 InvalidMessageDeliveries: float32(s.InvalidMessageDeliveries), 174 } 175 } 176 return newMap 177 }