github.com/amazechain/amc@v0.1.3/internal/sync/subscriber.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/amazechain/amc/common/hexutil"
     7  	"github.com/amazechain/amc/common/types"
     8  	"github.com/amazechain/amc/internal/p2p"
     9  	"github.com/amazechain/amc/internal/p2p/peers"
    10  	"github.com/amazechain/amc/log"
    11  	"github.com/amazechain/amc/utils"
    12  	"github.com/holiman/uint256"
    13  	"runtime/debug"
    14  	"strings"
    15  	"time"
    16  
    17  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    18  	"github.com/libp2p/go-libp2p/core/host"
    19  	"github.com/libp2p/go-libp2p/core/peer"
    20  	"go.opencensus.io/trace"
    21  	"google.golang.org/protobuf/proto"
    22  )
    23  
    24  const pubsubMessageTimeout = 30 * time.Second
    25  
    26  // wrappedVal represents a gossip validator which also returns an error along with the result.
    27  type wrappedVal func(context.Context, peer.ID, *pubsub.Message) (pubsub.ValidationResult, error)
    28  
    29  // subHandler represents handler for a given subscription.
    30  type subHandler func(context.Context, proto.Message) error
    31  
    32  // noopValidator is a no-op that only decodes the message, but does not check its contents.
    33  func (s *Service) noopValidator(_ context.Context, _ peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) {
    34  	m, err := s.decodePubsubMessage(msg)
    35  	if err != nil {
    36  		log.Debug("Could not decode message", "err", err)
    37  		return pubsub.ValidationReject, nil
    38  	}
    39  	msg.ValidatorData = m
    40  	return pubsub.ValidationAccept, nil
    41  }
    42  
    43  // Register PubSub subscribers
    44  func (s *Service) registerSubscribers(digest [4]byte) {
    45  	s.subscribe(
    46  		p2p.BlockTopicFormat,
    47  		s.validateBlockPubSub,
    48  		s.blockSubscriber,
    49  		digest,
    50  	)
    51  	//todo txs?
    52  	//s.subscribe(
    53  	//	p2p.TransactionTopicFormat,
    54  	//	digest,
    55  	//)
    56  }
    57  
    58  // subscribe to a given topic with a given validator and subscription handler.
    59  // The base protobuf message is used to initialize new messages for decoding.
    60  func (s *Service) subscribe(topic string, validator wrappedVal, handle subHandler, digest [4]byte) *pubsub.Subscription {
    61  	//todo
    62  	//genRoot := s.chain.GenesisBlock().Hash()
    63  	//_, e, err := utils.RetrieveForkDataFromDigest(digest, genRoot[:])
    64  	//if err != nil {
    65  	//	// Impossible condition as it would mean digest does not exist.
    66  	//	panic(err)
    67  	//}
    68  	base := p2p.GossipTopicMappings(topic)
    69  	if base == nil {
    70  		// Impossible condition as it would mean topic does not exist.
    71  		panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topic))
    72  	}
    73  	return s.subscribeWithBase(s.addDigestToTopic(topic, digest), validator, handle)
    74  }
    75  
    76  func (s *Service) subscribeWithBase(topic string, validator wrappedVal, handle subHandler) *pubsub.Subscription {
    77  	topic += s.cfg.p2p.Encoding().ProtocolSuffix()
    78  	//log := log.WithField("topic", topic)
    79  
    80  	// Do not resubscribe already seen subscriptions.
    81  	ok := s.subHandler.topicExists(topic)
    82  	if ok {
    83  		log.Debug(fmt.Sprintf("Provided topic already has an active subscription running: %s", topic), "topic", topic)
    84  		return nil
    85  	}
    86  
    87  	if err := s.cfg.p2p.PubSub().RegisterTopicValidator(s.wrapAndReportValidation(topic, validator)); err != nil {
    88  		log.Error("Could not register validator for topic", "topic", topic)
    89  		return nil
    90  	}
    91  
    92  	sub, err := s.cfg.p2p.SubscribeToTopic(topic)
    93  	if err != nil {
    94  		// Any error subscribing to a PubSub topic would be the result of a misconfiguration of
    95  		// libp2p PubSub library or a subscription request to a topic that fails to match the topic
    96  		// subscription filter.
    97  		log.Error("Could not subscribe topic", "topic", topic, "err", err)
    98  		return nil
    99  	}
   100  	s.subHandler.addTopic(sub.Topic(), sub)
   101  
   102  	// Pipeline decodes the incoming subscription data, runs the validation, and handles the
   103  	// message.
   104  	pipeline := func(msg *pubsub.Message) {
   105  		ctx, cancel := context.WithTimeout(s.ctx, pubsubMessageTimeout)
   106  		defer cancel()
   107  		ctx, span := trace.StartSpan(ctx, "sync.pubsub")
   108  		defer span.End()
   109  
   110  		defer func() {
   111  			if r := recover(); r != nil {
   112  				//tracing.AnnotateError(span, fmt.Errorf("panic occurred: %v", r))
   113  				log.Error("Panic occurred", "err", r, "topic", topic)
   114  				debug.PrintStack()
   115  			}
   116  		}()
   117  
   118  		span.AddAttributes(trace.StringAttribute("topic", topic))
   119  
   120  		if msg.ValidatorData == nil {
   121  			log.Error("Received nil message on pubsub")
   122  			messageFailedProcessingCounter.WithLabelValues(topic).Inc()
   123  			return
   124  		}
   125  
   126  		if err := handle(ctx, msg.ValidatorData.(proto.Message)); err != nil {
   127  			//tracing.AnnotateError(span, err)
   128  			log.Error("Could not handle p2p pubsub", "err", err, "topic", topic)
   129  			messageFailedProcessingCounter.WithLabelValues(topic).Inc()
   130  			return
   131  		}
   132  	}
   133  
   134  	// The main message loop for receiving incoming messages from this subscription.
   135  	messageLoop := func() {
   136  		for {
   137  			msg, err := sub.Next(s.ctx)
   138  			if err != nil {
   139  				// This should only happen when the context is cancelled or subscription is cancelled.
   140  				if err != pubsub.ErrSubscriptionCancelled { // Only log a warning on unexpected errors.
   141  					log.Warn("Subscription next failed", "err", err)
   142  				}
   143  				// Cancel subscription in the event of an error, as we are
   144  				// now exiting topic event loop.
   145  				sub.Cancel()
   146  				return
   147  			}
   148  
   149  			if msg.ReceivedFrom == s.cfg.p2p.PeerID() {
   150  				continue
   151  			}
   152  
   153  			go pipeline(msg)
   154  		}
   155  	}
   156  
   157  	go messageLoop()
   158  	log.Info("Subscribed to topic", "topic", topic)
   159  	return sub
   160  }
   161  
   162  // Wrap the pubsub validator with a metric monitoring function. This function increments the
   163  // appropriate counter if the particular message fails to validate.
   164  func (s *Service) wrapAndReportValidation(topic string, v wrappedVal) (string, pubsub.ValidatorEx) {
   165  	return topic, func(ctx context.Context, pid peer.ID, msg *pubsub.Message) (res pubsub.ValidationResult) {
   166  		defer s.handlePanic(ctx, msg)
   167  		res = pubsub.ValidationIgnore // Default: ignore any message that panics.
   168  		ctx, cancel := context.WithTimeout(ctx, pubsubMessageTimeout)
   169  		defer cancel()
   170  		messageReceivedCounter.WithLabelValues(topic).Inc()
   171  		if msg.Topic == nil {
   172  			messageFailedValidationCounter.WithLabelValues(topic).Inc()
   173  			return pubsub.ValidationReject
   174  		}
   175  		retDigest, err := p2p.ExtractGossipDigest(topic)
   176  		if err != nil {
   177  			log.Error(fmt.Sprintf("Invalid topic format of pubsub topic: %v", err), "topic", topic)
   178  			return pubsub.ValidationIgnore
   179  		}
   180  		currDigest, err := s.currentForkDigest()
   181  		if err != nil {
   182  			log.Error(fmt.Sprintf("Unable to retrieve fork data: %v", err), "topic", topic)
   183  			return pubsub.ValidationIgnore
   184  		}
   185  		if currDigest != retDigest {
   186  			log.Debug(fmt.Sprintf("Received message from outdated fork digest %#x", retDigest), "topic", topic)
   187  			return pubsub.ValidationIgnore
   188  		}
   189  		b, err := v(ctx, pid, msg)
   190  
   191  		var fields = make([]interface{}, 0)
   192  		fields = append(fields, "topic", topic)
   193  		fields = append(fields, "peer id", pid.String())
   194  		fields = append(fields, "multiaddress", multiAddr(pid, s.cfg.p2p.Peers()))
   195  		fields = append(fields, "agent", agentString(pid, s.cfg.p2p.Host()))
   196  		fields = append(fields, "gossip score", s.cfg.p2p.Peers().Scorers().GossipScorer().Score(pid))
   197  
   198  		if b == pubsub.ValidationReject {
   199  			if enableFullSSZDataLogging {
   200  				fields = append(fields, "message", hexutil.Encode(msg.Data))
   201  			}
   202  			fields = append(fields, "err", err)
   203  			log.Debug("Gossip message was rejected", fields...)
   204  			messageFailedValidationCounter.WithLabelValues(topic).Inc()
   205  		}
   206  		if b == pubsub.ValidationIgnore {
   207  			if err != nil {
   208  				log.Debug("Gossip message was ignored", fields...)
   209  			}
   210  			messageIgnoredValidationCounter.WithLabelValues(topic).Inc()
   211  		}
   212  		return b
   213  	}
   214  }
   215  
   216  // revalidate that our currently connected subnets are valid.
   217  func (s *Service) reValidateSubscriptions(subscriptions map[uint64]*pubsub.Subscription,
   218  	wantedSubs []uint64, topicFormat string, digest [4]byte) {
   219  	for k, v := range subscriptions {
   220  		var wanted bool
   221  		for _, idx := range wantedSubs {
   222  			if k == idx {
   223  				wanted = true
   224  				break
   225  			}
   226  		}
   227  		if !wanted && v != nil {
   228  			v.Cancel()
   229  			fullTopic := fmt.Sprintf(topicFormat, digest, k) + s.cfg.p2p.Encoding().ProtocolSuffix()
   230  			s.unSubscribeFromTopic(fullTopic)
   231  			delete(subscriptions, k)
   232  		}
   233  	}
   234  }
   235  
   236  func (s *Service) unSubscribeFromTopic(topic string) {
   237  	log.Debug("Unsubscribing from topic", "topic", topic)
   238  	if err := s.cfg.p2p.PubSub().UnregisterTopicValidator(topic); err != nil {
   239  		log.Error("Could not unregister topic validator", "err", err)
   240  	}
   241  	sub := s.subHandler.subForTopic(topic)
   242  	if sub != nil {
   243  		sub.Cancel()
   244  	}
   245  	s.subHandler.removeTopic(topic)
   246  	if err := s.cfg.p2p.LeaveTopic(topic); err != nil {
   247  		log.Error("Unable to leave topic", "err", err)
   248  	}
   249  }
   250  
   251  // Add fork digest to topic.
   252  func (_ *Service) addDigestToTopic(topic string, digest [4]byte) string {
   253  	if !strings.Contains(topic, "%x") {
   254  		log.Crit("Topic does not have appropriate formatter for digest")
   255  	}
   256  	return fmt.Sprintf(topic, digest)
   257  }
   258  
   259  // Add the digest and index to subnet topic.
   260  func (_ *Service) addDigestAndIndexToTopic(topic string, digest [4]byte, idx uint64) string {
   261  	if !strings.Contains(topic, "%x") {
   262  		log.Crit("Topic does not have appropriate formatter for digest")
   263  	}
   264  	return fmt.Sprintf(topic, digest, idx)
   265  }
   266  
   267  func (s *Service) currentForkDigest() ([4]byte, error) {
   268  	genRoot := s.cfg.chain.GenesisBlock().Header().Hash()
   269  	return utils.CreateForkDigest(s.cfg.chain.CurrentBlock().Number64(), genRoot)
   270  }
   271  
   272  // Checks if the provided digest matches up with the current supposed digest.
   273  func isDigestValid(digest [4]byte, blockNr *uint256.Int, genValRoot types.Hash) (bool, error) {
   274  	retDigest, err := utils.CreateForkDigest(blockNr, genValRoot)
   275  	if err != nil {
   276  		return false, err
   277  	}
   278  	//isNextEpoch, err := utils.CreateForkDigest(new(uint256.Int).AddUint64(blockNr, 1), genValRoot)
   279  	//if err != nil {
   280  	//	return false, err
   281  	//}
   282  
   283  	return retDigest == digest, nil
   284  }
   285  
   286  func agentString(pid peer.ID, hst host.Host) string {
   287  	agString := ""
   288  	ok := false
   289  	rawVersion, storeErr := hst.Peerstore().Get(pid, "AgentVersion")
   290  	agString, ok = rawVersion.(string)
   291  	if storeErr != nil || !ok {
   292  		agString = ""
   293  	}
   294  	return agString
   295  }
   296  
   297  func multiAddr(pid peer.ID, stat *peers.Status) string {
   298  	addrs, err := stat.Address(pid)
   299  	if err != nil || addrs == nil {
   300  		return ""
   301  	}
   302  	return addrs.String()
   303  }