github.com/amazechain/amc@v0.1.3/internal/p2p/pubsub.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"github.com/amazechain/amc/api/protocol/msg_proto"
     7  	"github.com/amazechain/amc/utils"
     8  	"strings"
     9  	"time"
    10  
    11  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    12  	pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb"
    13  	"github.com/libp2p/go-libp2p/core/peer"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  const (
    18  	// overlay parameters
    19  	gossipSubD   = 8  // topic stable mesh target count
    20  	gossipSubDlo = 6  // topic stable mesh low watermark
    21  	gossipSubDhi = 12 // topic stable mesh high watermark
    22  
    23  	// gossip parameters
    24  	gossipSubMcacheLen    = 6   // number of windows to retain full messages in cache for `IWANT` responses
    25  	gossipSubMcacheGossip = 3   // number of windows to gossip about
    26  	gossipSubSeenTTL      = 550 // number of heartbeat intervals to retain message IDs
    27  
    28  	// fanout ttl
    29  	gossipSubFanoutTTL = 60000000000 // TTL for fanout maps for topics we are not subscribed to but have published to, in nano seconds
    30  
    31  	// heartbeat interval
    32  	gossipSubHeartbeatInterval = 700 * time.Millisecond // frequency of heartbeat, milliseconds
    33  
    34  	// misc
    35  	rSubD = 8 // random gossip target
    36  
    37  	//todo
    38  	GossipMaxSize = 10000000
    39  )
    40  
    41  var errInvalidTopic = errors.New("invalid topic format")
    42  
    43  // Specifies the fixed size context length.
    44  const digestLength = 4
    45  
    46  // Specifies the prefix for any pubsub topic.
    47  const gossipTopicPrefix = "/amc/"
    48  
    49  // JoinTopic will join PubSub topic, if not already joined.
    50  func (s *Service) JoinTopic(topic string, opts ...pubsub.TopicOpt) (*pubsub.Topic, error) {
    51  	s.joinedTopicsLock.Lock()
    52  	defer s.joinedTopicsLock.Unlock()
    53  
    54  	if _, ok := s.joinedTopics[topic]; !ok {
    55  		topicHandle, err := s.pubsub.Join(topic, opts...)
    56  		if err != nil {
    57  			return nil, err
    58  		}
    59  		s.joinedTopics[topic] = topicHandle
    60  	}
    61  
    62  	return s.joinedTopics[topic], nil
    63  }
    64  
    65  // LeaveTopic closes topic and removes corresponding handler from list of joined topics.
    66  // This method will return error if there are outstanding event handlers or subscriptions.
    67  func (s *Service) LeaveTopic(topic string) error {
    68  	s.joinedTopicsLock.Lock()
    69  	defer s.joinedTopicsLock.Unlock()
    70  
    71  	if t, ok := s.joinedTopics[topic]; ok {
    72  		if err := t.Close(); err != nil {
    73  			return err
    74  		}
    75  		delete(s.joinedTopics, topic)
    76  	}
    77  	return nil
    78  }
    79  
    80  // PublishToTopic joins (if necessary) and publishes a message to a PubSub topic.
    81  func (s *Service) PublishToTopic(ctx context.Context, topic string, data []byte, opts ...pubsub.PubOpt) error {
    82  	topicHandle, err := s.JoinTopic(topic)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	// Wait for at least 1 peer to be available to receive the published message.
    88  	for {
    89  		if len(topicHandle.ListPeers()) > 0 || s.cfg.MinSyncPeers == 0 {
    90  			return topicHandle.Publish(ctx, data, opts...)
    91  		}
    92  		select {
    93  		case <-ctx.Done():
    94  			return errors.Wrapf(ctx.Err(), "unable to find requisite number of peers for topic %s, 0 peers found to publish to", topic)
    95  		default:
    96  			time.Sleep(100 * time.Millisecond)
    97  		}
    98  	}
    99  }
   100  
   101  // SubscribeToTopic joins (if necessary) and subscribes to PubSub topic.
   102  func (s *Service) SubscribeToTopic(topic string, opts ...pubsub.SubOpt) (*pubsub.Subscription, error) {
   103  
   104  	topicHandle, err := s.JoinTopic(topic)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	scoringParams, err := s.topicScoreParams(topic)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	if scoringParams != nil {
   114  		if err := topicHandle.SetScoreParams(scoringParams); err != nil {
   115  			return nil, err
   116  		}
   117  		logGossipParameters(topic, scoringParams)
   118  	}
   119  	return topicHandle.Subscribe(opts...)
   120  }
   121  
   122  // peerInspector will scrape all the relevant scoring data and add it to our
   123  // peer handler.
   124  func (s *Service) peerInspector(peerMap map[peer.ID]*pubsub.PeerScoreSnapshot) {
   125  	// Iterate through all the connected peers and through any of their
   126  	// relevant topics.
   127  	for pid, snap := range peerMap {
   128  		s.peers.Scorers().GossipScorer().SetGossipData(pid, snap.Score,
   129  			snap.BehaviourPenalty, convertTopicScores(snap.Topics))
   130  	}
   131  }
   132  
   133  // Creates a list of pubsub options to configure out router with.
   134  func (s *Service) pubsubOptions() []pubsub.Option {
   135  	psOpts := []pubsub.Option{
   136  		pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign),
   137  		pubsub.WithNoAuthor(),
   138  		pubsub.WithMessageIdFn(func(pmsg *pubsubpb.Message) string {
   139  			return MsgID(s.genesisHash, pmsg)
   140  		}),
   141  		pubsub.WithSubscriptionFilter(s),
   142  		pubsub.WithPeerOutboundQueueSize(pubsubQueueSize),
   143  		pubsub.WithMaxMessageSize(GossipMaxSize),
   144  		pubsub.WithValidateQueueSize(pubsubQueueSize),
   145  		pubsub.WithPeerScore(peerScoringParams()),
   146  		pubsub.WithPeerScoreInspect(s.peerInspector, time.Minute),
   147  		pubsub.WithGossipSubParams(pubsubGossipParam()),
   148  		pubsub.WithRawTracer(gossipTracer{host: s.host}),
   149  	}
   150  	return psOpts
   151  }
   152  
   153  // creates a custom gossipsub parameter set.
   154  func pubsubGossipParam() pubsub.GossipSubParams {
   155  	gParams := pubsub.DefaultGossipSubParams()
   156  	gParams.Dlo = gossipSubDlo
   157  	gParams.D = gossipSubD
   158  	gParams.HeartbeatInterval = gossipSubHeartbeatInterval
   159  	gParams.HistoryLength = gossipSubMcacheLen
   160  	gParams.HistoryGossip = gossipSubMcacheGossip
   161  	return gParams
   162  }
   163  
   164  // We have to unfortunately set this globally in order
   165  // to configure our message id time-cache rather than instantiating
   166  // it with a router instance.
   167  func setPubSubParameters() {
   168  	pubsub.TimeCacheDuration = 550 * gossipSubHeartbeatInterval
   169  }
   170  
   171  // convert from libp2p's internal schema to a compatible prysm protobuf format.
   172  func convertTopicScores(topicMap map[string]*pubsub.TopicScoreSnapshot) map[string]*msg_proto.TopicScoreSnapshot {
   173  	newMap := make(map[string]*msg_proto.TopicScoreSnapshot, len(topicMap))
   174  	for t, s := range topicMap {
   175  		newMap[t] = &msg_proto.TopicScoreSnapshot{
   176  			TimeInMesh:               uint64(s.TimeInMesh.Milliseconds()),
   177  			FirstMessageDeliveries:   float32(s.FirstMessageDeliveries),
   178  			MeshMessageDeliveries:    float32(s.MeshMessageDeliveries),
   179  			InvalidMessageDeliveries: float32(s.InvalidMessageDeliveries),
   180  		}
   181  	}
   182  	return newMap
   183  }
   184  
   185  // ExtractGossipDigest extracts the relevant fork digest from the gossip topic.
   186  // Topics are in the form of /eth2/{fork-digest}/{topic} and this method extracts the
   187  // fork digest from the topic string to a 4 byte array.
   188  func ExtractGossipDigest(topic string) ([4]byte, error) {
   189  	// Ensure the topic prefix is correct.
   190  	if len(topic) < len(gossipTopicPrefix)+1 || topic[:len(gossipTopicPrefix)] != gossipTopicPrefix {
   191  		return [4]byte{}, errInvalidTopic
   192  	}
   193  	start := len(gossipTopicPrefix)
   194  	end := strings.Index(topic[start:], "/")
   195  	if end == -1 { // Ensure a topic suffix exists.
   196  		return [4]byte{}, errInvalidTopic
   197  	}
   198  	end += start
   199  	strDigest := topic[start:end]
   200  	digest, err := hex.DecodeString(strDigest)
   201  	if err != nil {
   202  		return [4]byte{}, err
   203  	}
   204  	if len(digest) != digestLength {
   205  		return [4]byte{}, errors.Errorf("invalid digest length wanted %d but got %d", digestLength, len(digest))
   206  	}
   207  	return utils.ToBytes4(digest), nil
   208  }