github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/tracer/gossipSubMeshTracer.go (about)

     1  package tracer
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"sync"
     7  	"time"
     8  
     9  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    10  	"github.com/libp2p/go-libp2p/core/peer"
    11  	"github.com/libp2p/go-libp2p/core/protocol"
    12  	"github.com/rs/zerolog"
    13  
    14  	"github.com/onflow/flow-go/module"
    15  	"github.com/onflow/flow-go/module/component"
    16  	"github.com/onflow/flow-go/module/irrecoverable"
    17  	"github.com/onflow/flow-go/module/metrics"
    18  	"github.com/onflow/flow-go/network"
    19  	"github.com/onflow/flow-go/network/channels"
    20  	"github.com/onflow/flow-go/network/p2p"
    21  	p2pconfig "github.com/onflow/flow-go/network/p2p/config"
    22  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    23  	"github.com/onflow/flow-go/network/p2p/tracer/internal"
    24  	"github.com/onflow/flow-go/utils/logging"
    25  )
    26  
    27  const (
    28  	// MeshLogIntervalMsg is the message logged by the tracer every logInterval.
    29  	MeshLogIntervalMsg = "topic mesh peers of local node since last heartbeat"
    30  
    31  	// MeshLogIntervalWarnMsg is the message logged by the tracer every logInterval if there are unknown peers in the mesh.
    32  	MeshLogIntervalWarnMsg = "unknown peers in topic mesh peers of local node since last heartbeat"
    33  
    34  	// defaultLastHighestIHaveRPCSizeResetInterval is the interval that we reset the tracker of max ihave size sent back
    35  	// to a default. We use ihave message max size to determine the health of requested iwants from remote peers. However,
    36  	// we don't desire an ihave size anomaly to persist forever, hence, we reset it back to a default every minute.
    37  	// The choice of the interval to be a minute is in harmony with the GossipSub decay interval.
    38  	defaultLastHighestIHaveRPCSizeResetInterval = time.Minute
    39  )
    40  
    41  // The GossipSubMeshTracer component in the GossipSub pubsub.RawTracer that is designed to track the local
    42  // mesh peers for each topic. By logging the mesh peers and updating the local mesh size metric, the GossipSubMeshTracer
    43  // provides insights into the behavior of the topology.
    44  //
    45  // This component also provides real-time and historical visibility into the topology.
    46  // The GossipSubMeshTracer logs the mesh peers of the local node for each topic
    47  // at a regular interval, enabling users to monitor the state of the mesh network and take appropriate action.
    48  // Additionally, it allows users to configure the logging interval.
    49  type GossipSubMeshTracer struct {
    50  	component.Component
    51  	topicMeshMu                  sync.RWMutex                    // to protect topicMeshMap
    52  	topicMeshMap                 map[string]map[peer.ID]struct{} // map of local mesh peers by topic.
    53  	logger                       zerolog.Logger
    54  	idProvider                   module.IdentityProvider
    55  	loggerInterval               time.Duration
    56  	metrics                      module.LocalGossipSubRouterMetrics
    57  	rpcSentTracker               *internal.RPCSentTracker
    58  	duplicateMessageTrackerCache *internal.DuplicateMessageTrackerCache
    59  }
    60  
    61  var _ p2p.PubSubTracer = (*GossipSubMeshTracer)(nil)
    62  
    63  type RpcSentTrackerConfig struct {
    64  	CacheSize            uint32 `validate:"gt=0"`
    65  	WorkerQueueCacheSize uint32 `validate:"gt=0"`
    66  	WorkerQueueNumber    int    `validate:"gt=0"`
    67  }
    68  
    69  type DuplicateMessageTrackerCacheConfig struct {
    70  	CacheSize uint32  `validate:"gt=0"`
    71  	Decay     float64 `validate:"gt=0"`
    72  }
    73  
    74  type GossipSubMeshTracerConfig struct {
    75  	network.NetworkingType             `validate:"required"`
    76  	metrics.HeroCacheMetricsFactory    `validate:"required"`
    77  	Logger                             zerolog.Logger                          `validate:"required"`
    78  	Metrics                            module.LocalGossipSubRouterMetrics      `validate:"required"`
    79  	IDProvider                         module.IdentityProvider                 `validate:"required"`
    80  	LoggerInterval                     time.Duration                           `validate:"required"`
    81  	DuplicateMessageTrackerCacheConfig p2pconfig.DuplicateMessageTrackerConfig `validate:"required"`
    82  	RpcSentTracker                     RpcSentTrackerConfig                    `validate:"required"`
    83  }
    84  
    85  // NewGossipSubMeshTracer creates a new *GossipSubMeshTracer.
    86  // Args:
    87  // - *GossipSubMeshTracerConfig: the mesh tracer config.
    88  // Returns:
    89  // - *GossipSubMeshTracer: new mesh tracer.
    90  func NewGossipSubMeshTracer(config *GossipSubMeshTracerConfig) *GossipSubMeshTracer {
    91  	lg := config.Logger.With().Str("component", "gossipsub_topology_tracer").Logger()
    92  	rpcSentTracker := internal.NewRPCSentTracker(&internal.RPCSentTrackerConfig{
    93  		Logger:                             lg,
    94  		RPCSentCacheSize:                   config.RpcSentTracker.CacheSize,
    95  		RPCSentCacheCollector:              metrics.GossipSubRPCSentTrackerMetricFactory(config.HeroCacheMetricsFactory, config.NetworkingType),
    96  		WorkerQueueCacheCollector:          metrics.GossipSubRPCSentTrackerQueueMetricFactory(config.HeroCacheMetricsFactory, config.NetworkingType),
    97  		WorkerQueueCacheSize:               config.RpcSentTracker.WorkerQueueCacheSize,
    98  		NumOfWorkers:                       config.RpcSentTracker.WorkerQueueNumber,
    99  		LastHighestIhavesSentResetInterval: defaultLastHighestIHaveRPCSizeResetInterval,
   100  	})
   101  	g := &GossipSubMeshTracer{
   102  		topicMeshMap:   make(map[string]map[peer.ID]struct{}),
   103  		idProvider:     config.IDProvider,
   104  		metrics:        config.Metrics,
   105  		logger:         lg,
   106  		loggerInterval: config.LoggerInterval,
   107  		rpcSentTracker: rpcSentTracker,
   108  		duplicateMessageTrackerCache: internal.NewDuplicateMessageTrackerCache(
   109  			config.DuplicateMessageTrackerCacheConfig.CacheSize,
   110  			config.DuplicateMessageTrackerCacheConfig.Decay,
   111  			config.DuplicateMessageTrackerCacheConfig.SkipDecayThreshold,
   112  			config.Logger,
   113  			metrics.GossipSubDuplicateMessageTrackerCacheMetricFactory(config.HeroCacheMetricsFactory, config.NetworkingType),
   114  		),
   115  	}
   116  
   117  	g.Component = component.NewComponentManagerBuilder().
   118  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   119  			ready()
   120  			g.logLoop(ctx)
   121  		}).
   122  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   123  			ready()
   124  			lg.Debug().Msg("starting rpc sent tracker")
   125  			g.rpcSentTracker.Start(ctx)
   126  			lg.Debug().Msg("rpc sent tracker started")
   127  
   128  			<-g.rpcSentTracker.Done()
   129  			lg.Debug().Msg("rpc sent tracker stopped")
   130  		}).
   131  		Build()
   132  
   133  	return g
   134  }
   135  
   136  // GetLocalMeshPeers returns the local mesh peers for the given topic.
   137  // Args:
   138  // - topic: the topic.
   139  // Returns:
   140  // - []peer.ID: the local mesh peers for the given topic.
   141  func (t *GossipSubMeshTracer) GetLocalMeshPeers(topic channels.Topic) []peer.ID {
   142  	t.topicMeshMu.RLock()
   143  	defer t.topicMeshMu.RUnlock()
   144  
   145  	peers := make([]peer.ID, 0, len(t.topicMeshMap[topic.String()]))
   146  	for p := range t.topicMeshMap[topic.String()] {
   147  		peers = append(peers, p)
   148  	}
   149  	return peers
   150  }
   151  
   152  // Graft is called by GossipSub when a peer is added to a topic mesh. The tracer uses this to track the mesh peers.
   153  func (t *GossipSubMeshTracer) Graft(p peer.ID, topic string) {
   154  	t.metrics.OnPeerGraftTopic(topic)
   155  	t.topicMeshMu.Lock()
   156  	defer t.topicMeshMu.Unlock()
   157  
   158  	lg := t.logger.With().Str("topic", topic).Str("peer_id", p2plogging.PeerId(p)).Logger()
   159  
   160  	if _, ok := t.topicMeshMap[topic]; !ok {
   161  		t.topicMeshMap[topic] = make(map[peer.ID]struct{})
   162  	}
   163  	t.topicMeshMap[topic][p] = struct{}{}
   164  	meshSize := len(t.topicMeshMap[topic])
   165  
   166  	t.metrics.OnLocalMeshSizeUpdated(topic, meshSize)
   167  	lg = lg.With().Int("mesh_size", meshSize).Logger()
   168  
   169  	id, exists := t.idProvider.ByPeerID(p)
   170  	if !exists {
   171  		lg.Warn().
   172  			Bool(logging.KeySuspicious, true).
   173  			Msg("grafted peer not found in identity provider")
   174  		return
   175  	}
   176  
   177  	lg.Debug().Hex("flow_id", logging.ID(id.NodeID)).Str("role", id.Role.String()).Msg("grafted peer")
   178  }
   179  
   180  // Prune is called by GossipSub when a peer is removed from a topic mesh. The tracer uses this to track the mesh peers.
   181  func (t *GossipSubMeshTracer) Prune(p peer.ID, topic string) {
   182  	t.metrics.OnPeerPruneTopic(topic)
   183  	t.topicMeshMu.Lock()
   184  	defer t.topicMeshMu.Unlock()
   185  
   186  	lg := t.logger.With().Str("topic", topic).Str("peer_id", p2plogging.PeerId(p)).Logger()
   187  
   188  	if _, ok := t.topicMeshMap[topic]; !ok {
   189  		return
   190  	}
   191  	delete(t.topicMeshMap[topic], p)
   192  
   193  	meshSize := len(t.topicMeshMap[topic])
   194  	t.metrics.OnLocalMeshSizeUpdated(topic, meshSize)
   195  	lg = lg.With().Int("mesh_size", meshSize).Logger()
   196  
   197  	id, exists := t.idProvider.ByPeerID(p)
   198  	if !exists {
   199  		lg.Warn().
   200  			Bool(logging.KeySuspicious, true).
   201  			Msg("pruned peer not found in identity provider")
   202  
   203  		return
   204  	}
   205  
   206  	lg.Debug().Hex("flow_id", logging.ID(id.NodeID)).Str("role", id.Role.String()).Msg("pruned peer")
   207  }
   208  
   209  // SendRPC is called by GossipSub when a RPC is sent. Currently, the GossipSubMeshTracer tracks iHave RPC messages that have been sent.
   210  // This function can be updated to track other control messages in the future as required.
   211  func (t *GossipSubMeshTracer) SendRPC(rpc *pubsub.RPC, p peer.ID) {
   212  	err := t.rpcSentTracker.Track(rpc)
   213  	if err != nil {
   214  		t.logger.Err(err).Bool(logging.KeyNetworkingSecurity, true).Msg("failed to track sent pubsbub rpc")
   215  	}
   216  
   217  	msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0
   218  	if rpc.Control != nil {
   219  		ihaveCount = len(rpc.Control.Ihave)
   220  		iwantCount = len(rpc.Control.Iwant)
   221  		graftCount = len(rpc.Control.Graft)
   222  		pruneCount = len(rpc.Control.Prune)
   223  	}
   224  	msgCount = len(rpc.Publish)
   225  	t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount)
   226  	if t.logger.GetLevel() == zerolog.TraceLevel {
   227  		t.logger.Trace().
   228  			Str("remote_peer_id", p2plogging.PeerId(p)).
   229  			Int("subscription_option_count", len(rpc.Subscriptions)).
   230  			Int("publish_message_count", msgCount).
   231  			Int("ihave_size", ihaveCount).
   232  			Int("iwant_size", iwantCount).
   233  			Int("graft_size", graftCount).
   234  			Int("prune_size", pruneCount).
   235  			Msg("sent pubsub rpc")
   236  	}
   237  
   238  	t.metrics.OnRpcSent(msgCount, ihaveCount, iwantCount, graftCount, pruneCount)
   239  }
   240  
   241  // AddPeer is called by GossipSub as a callback when a peer is added to the local node on a protocol, i.e., the local node is connected to the peer on a protocol.
   242  // The peer may or may not be subscribed to any topic.
   243  func (t *GossipSubMeshTracer) AddPeer(p peer.ID, proto protocol.ID) {
   244  	if t.logger.GetLevel() == zerolog.TraceLevel {
   245  		t.logger.Trace().
   246  			Str("local_peer_id", p2plogging.PeerId(p)).
   247  			Str("protocol", string(proto)).
   248  			Msg("peer added")
   249  	}
   250  	t.metrics.OnPeerAddedToProtocol(string(proto))
   251  }
   252  
   253  // RemovePeer is called by GossipSub as a callback when a peer is removed from the local node,
   254  // i.e., the local node is no longer connected to the peer.
   255  func (t *GossipSubMeshTracer) RemovePeer(p peer.ID) {
   256  	t.metrics.OnPeerRemovedFromProtocol()
   257  	if t.logger.GetLevel() == zerolog.TraceLevel {
   258  		t.logger.Trace().
   259  			Str("local_peer_id", p2plogging.PeerId(p)).
   260  			Msg("peer removed")
   261  	}
   262  }
   263  
   264  // Join is called by GossipSub as a callback when the local node joins a topic.
   265  func (t *GossipSubMeshTracer) Join(topic string) {
   266  	t.metrics.OnLocalPeerJoinedTopic()
   267  	if t.logger.GetLevel() == zerolog.TraceLevel {
   268  		t.logger.Trace().
   269  			Str("topic", topic).
   270  			Msg("local peer joined topic")
   271  	}
   272  }
   273  
   274  // Leave is called by GossipSub as a callback when the local node leaves a topic.
   275  func (t *GossipSubMeshTracer) Leave(topic string) {
   276  	t.metrics.OnLocalPeerLeftTopic()
   277  	if t.logger.GetLevel() == zerolog.TraceLevel {
   278  		t.logger.Trace().
   279  			Str("topic", topic).
   280  			Msg("local peer left topic")
   281  	}
   282  }
   283  
   284  // ValidateMessage is called by GossipSub as a callback when a message is received by the local node and entered the validation phase.
   285  // As the result of the validation, the message may be rejected or passed to the application (i.e., Flow protocol).
   286  func (t *GossipSubMeshTracer) ValidateMessage(msg *pubsub.Message) {
   287  	size := len(msg.Data)
   288  	t.metrics.OnMessageEnteredValidation(size)
   289  
   290  	if t.logger.GetLevel() > zerolog.TraceLevel {
   291  		return // return fast if we are not logging at trace level
   292  	}
   293  
   294  	lg := t.logger.With().Logger()
   295  	if msg.Topic != nil {
   296  		lg = lg.With().Str("topic", *msg.Topic).Logger()
   297  	}
   298  	from, err := peer.IDFromBytes(msg.From)
   299  	if err == nil {
   300  		lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger()
   301  	}
   302  
   303  	lg.Trace().
   304  		Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)).
   305  		Int("message_size", size).
   306  		Msg("received pubsub message entered validation phase")
   307  }
   308  
   309  // DeliverMessage is called by GossipSub as a callback when the local node delivers a message to all subscribers of the topic.
   310  func (t *GossipSubMeshTracer) DeliverMessage(msg *pubsub.Message) {
   311  	size := len(msg.Data)
   312  	t.metrics.OnMessageDeliveredToAllSubscribers(size)
   313  
   314  	if t.logger.GetLevel() > zerolog.TraceLevel {
   315  		return // return fast if we are not logging at trace level
   316  	}
   317  
   318  	lg := t.logger.With().Logger()
   319  	if msg.Topic != nil {
   320  		lg = lg.With().Str("topic", *msg.Topic).Logger()
   321  	}
   322  	from, err := peer.IDFromBytes(msg.From)
   323  	if err == nil {
   324  		lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger()
   325  	}
   326  
   327  	lg.Trace().
   328  		Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)).
   329  		Int("message_size", len(msg.Data)).
   330  		Msg("delivered pubsub message to all subscribers")
   331  }
   332  
   333  // RejectMessage is called by GossipSub as a callback when a message is rejected by the local node.
   334  // The message may be rejected for a variety of reasons, but the most common reason is that the message is invalid with respect to signature.
   335  // Any message that arrives at the local node should contain the peer id of the source (i.e., the peer that created the message), the
   336  // networking public key of the source, and the signature of the message. The local node uses this information to verify the message.
   337  // If any of the information is missing or invalid, the message is rejected.
   338  func (t *GossipSubMeshTracer) RejectMessage(msg *pubsub.Message, reason string) {
   339  	size := len(msg.Data)
   340  	t.metrics.OnMessageRejected(size, reason)
   341  
   342  	if t.logger.GetLevel() > zerolog.TraceLevel {
   343  		return // return fast if we are not logging at trace level
   344  	}
   345  
   346  	lg := t.logger.With().Logger()
   347  	if msg.Topic != nil {
   348  		lg = lg.With().Str("topic", *msg.Topic).Logger()
   349  	}
   350  	from, err := peer.IDFromBytes(msg.From)
   351  	if err == nil {
   352  		lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger()
   353  	}
   354  
   355  	lg.Trace().
   356  		Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)).
   357  		Int("message_size", size).
   358  		Msg("rejected pubsub message")
   359  
   360  }
   361  
   362  // DuplicateMessage is called by GossipSub as a callback when a duplicate message is received by the local node.
   363  func (t *GossipSubMeshTracer) DuplicateMessage(msg *pubsub.Message) {
   364  	size := len(msg.Data)
   365  	t.metrics.OnMessageDuplicate(size)
   366  
   367  	if t.logger.GetLevel() > zerolog.TraceLevel {
   368  		return // return fast if we are not logging at trace level
   369  	}
   370  
   371  	lg := t.logger.With().Logger()
   372  	if msg.Topic != nil {
   373  		lg = lg.With().Str("topic", *msg.Topic).Logger()
   374  	}
   375  	from, err := peer.IDFromBytes(msg.From)
   376  	if err == nil {
   377  		lg = lg.With().Str("remote_peer_id", p2plogging.PeerId(from)).Logger()
   378  	}
   379  
   380  	count, err := t.duplicateMessageTrackerCache.DuplicateMessageReceived(msg.ReceivedFrom)
   381  	if err != nil {
   382  		t.logger.Fatal().
   383  			Err(err).
   384  			Bool(logging.KeyNetworkingSecurity, true).
   385  			Msg("failed to increment gossipsub duplicate message tracker count for peer")
   386  		return
   387  	}
   388  
   389  	lg.Trace().
   390  		Str("received_from", p2plogging.PeerId(msg.ReceivedFrom)).
   391  		Int("message_size", size).
   392  		Float64("duplicate_message_count", count).
   393  		Msg("received duplicate pubsub message")
   394  
   395  }
   396  
   397  // ThrottlePeer is called by GossipSub when a peer is throttled by the local node, i.e., the local node is not accepting any
   398  // pubsub message from the peer but may still accept control messages.
   399  func (t *GossipSubMeshTracer) ThrottlePeer(p peer.ID) {
   400  	t.logger.Warn().
   401  		Bool(logging.KeyNetworkingSecurity, true).
   402  		Str("remote_peer_id", p2plogging.PeerId(p)).
   403  		Msg("throttled peer; no longer accepting pubsub messages from peer, but may still accept control messages")
   404  	t.metrics.OnPeerThrottled()
   405  }
   406  
   407  // RecvRPC is called by GossipSub as a callback when an inbound RPC message is received by the local node,
   408  // note that the RPC already passed the RPC inspection, hence its statistics may be different from the RPC inspector metrics, as
   409  // the RPC inspector metrics are updated before the RPC inspection, and the RPC may gone through truncation or rejection.
   410  // This callback tracks the RPC messages as they are completely received by the local GossipSub router.
   411  func (t *GossipSubMeshTracer) RecvRPC(rpc *pubsub.RPC) {
   412  	msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0
   413  	if rpc.Control != nil {
   414  		ihaveCount = len(rpc.Control.Ihave)
   415  		iwantCount = len(rpc.Control.Iwant)
   416  		graftCount = len(rpc.Control.Graft)
   417  		pruneCount = len(rpc.Control.Prune)
   418  	}
   419  	msgCount = len(rpc.Publish)
   420  	t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount)
   421  	if t.logger.GetLevel() == zerolog.TraceLevel {
   422  		t.logger.Trace().
   423  			Int("subscription_option_count", len(rpc.Subscriptions)).
   424  			Int("publish_message_count", msgCount).
   425  			Int("ihave_size", ihaveCount).
   426  			Int("iwant_size", iwantCount).
   427  			Int("graft_size", graftCount).
   428  			Int("prune_size", pruneCount).
   429  			Msg("received pubsub rpc")
   430  	}
   431  }
   432  
   433  // DropRPC is called by GossipSub as a callback when an outbound RPC message is dropped by the local node, typically because the local node
   434  // outbound message queue is full; or the RPC is big and the local node cannot fragment it.
   435  func (t *GossipSubMeshTracer) DropRPC(rpc *pubsub.RPC, p peer.ID) {
   436  	msgCount, ihaveCount, iwantCount, graftCount, pruneCount := 0, 0, 0, 0, 0
   437  	if rpc.Control != nil {
   438  		ihaveCount = len(rpc.Control.Ihave)
   439  		iwantCount = len(rpc.Control.Iwant)
   440  		graftCount = len(rpc.Control.Graft)
   441  		pruneCount = len(rpc.Control.Prune)
   442  	}
   443  	msgCount = len(rpc.Publish)
   444  	t.metrics.OnRpcReceived(msgCount, ihaveCount, iwantCount, graftCount, pruneCount)
   445  	if t.logger.GetLevel() == zerolog.TraceLevel {
   446  		t.logger.Warn().
   447  			Bool(logging.KeyNetworkingSecurity, true).
   448  			Str("remote_peer_id", p2plogging.PeerId(p)).
   449  			Int("subscription_option_count", len(rpc.Subscriptions)).
   450  			Int("publish_message_count", msgCount).
   451  			Int("ihave_size", ihaveCount).
   452  			Int("iwant_size", iwantCount).
   453  			Int("graft_size", graftCount).
   454  			Int("prune_size", pruneCount).
   455  			Msg("outbound rpc dropped")
   456  	}
   457  	t.metrics.OnOutboundRpcDropped()
   458  }
   459  
   460  // UndeliverableMessage is called by GossipSub as a callback when a message is dropped by the local node, typically because the local node
   461  // outbound message queue is full; or the message is big and the local node cannot fragment it.
   462  func (t *GossipSubMeshTracer) UndeliverableMessage(msg *pubsub.Message) {
   463  	t.logger.Warn().
   464  		Bool(logging.KeyNetworkingSecurity, true).
   465  		Str("topic", *msg.Topic).
   466  		Str("remote_peer_id", p2plogging.PeerId(msg.ReceivedFrom)).
   467  		Int("message_size", len(msg.Data)).
   468  		Msg("undeliverable pubsub message")
   469  	t.metrics.OnUndeliveredMessage()
   470  }
   471  
   472  // WasIHaveRPCSent returns true if an iHave control message for the messageID was sent, otherwise false.
   473  func (t *GossipSubMeshTracer) WasIHaveRPCSent(messageID string) bool {
   474  	return t.rpcSentTracker.WasIHaveRPCSent(messageID)
   475  }
   476  
   477  // LastHighestIHaveRPCSize returns the last highest RPC iHave message sent.
   478  func (t *GossipSubMeshTracer) LastHighestIHaveRPCSize() int64 {
   479  	return t.rpcSentTracker.LastHighestIHaveRPCSize()
   480  }
   481  
   482  // DuplicateMessageCount returns the current duplicate message count for the peer.
   483  func (t *GossipSubMeshTracer) DuplicateMessageCount(peerID peer.ID) float64 {
   484  	count, found, err := t.duplicateMessageTrackerCache.GetWithInit(peerID)
   485  	if err != nil {
   486  		t.logger.Fatal().
   487  			Err(err).
   488  			Bool(logging.KeyNetworkingSecurity, true).
   489  			Str("peer_id", p2plogging.PeerId(peerID)).
   490  			Msg("failed to get duplicate message count for peer")
   491  		return 0
   492  	}
   493  	if !found {
   494  		t.logger.Fatal().
   495  			Err(err).
   496  			Bool(logging.KeyNetworkingSecurity, true).
   497  			Str("peer_id", peerID.String()).
   498  			Msg("failed to initialize duplicate message count for peer during get with init")
   499  		return 0
   500  	}
   501  	return count
   502  }
   503  
   504  // logLoop logs the mesh peers of the local node for each topic at a regular interval.
   505  func (t *GossipSubMeshTracer) logLoop(ctx irrecoverable.SignalerContext) {
   506  	ticker := time.NewTicker(t.loggerInterval)
   507  	defer ticker.Stop()
   508  
   509  	for {
   510  		select {
   511  		case <-ctx.Done():
   512  			return
   513  		default:
   514  		}
   515  
   516  		select {
   517  		case <-ctx.Done():
   518  			return
   519  		case <-ticker.C:
   520  			t.logPeers()
   521  		}
   522  	}
   523  }
   524  
   525  // logPeers logs the mesh peers of the local node for each topic.
   526  // Note that based on GossipSub parameters, we expect to have between 6 and 12 peers in the mesh for each topic.
   527  // Hence, choosing a heartbeat interval in the order of minutes should be sufficient to log the mesh peers of the local node.
   528  // Also, note that the mesh peers are also logged reactively when a peer is added or removed from the mesh.
   529  func (t *GossipSubMeshTracer) logPeers() {
   530  	t.topicMeshMu.RLock()
   531  	defer t.topicMeshMu.RUnlock()
   532  	for topic := range t.topicMeshMap {
   533  		shouldWarn := false // whether we should warn about the mesh state
   534  
   535  		topicPeers := zerolog.Dict()
   536  
   537  		peerIndex := -1 // index to keep track of peer info in different logging dictionaries.
   538  		for p := range t.topicMeshMap[topic] {
   539  			peerIndex++
   540  			id, exists := t.idProvider.ByPeerID(p)
   541  
   542  			if !exists {
   543  				shouldWarn = true
   544  				topicPeers = topicPeers.Str(strconv.Itoa(peerIndex), fmt.Sprintf("pid=%s, flow_id=unknown, role=unknown", p2plogging.PeerId(p)))
   545  				continue
   546  			}
   547  
   548  			topicPeers = topicPeers.Str(strconv.Itoa(peerIndex), fmt.Sprintf("pid=%s, flow_id=%x, role=%s", p2plogging.PeerId(p), id.NodeID, id.Role.String()))
   549  		}
   550  
   551  		lg := t.logger.With().
   552  			Dur("heartbeat_interval", t.loggerInterval).
   553  			Str("topic", topic).
   554  			Dict("topic_mesh", topicPeers).
   555  			Logger()
   556  
   557  		if shouldWarn {
   558  			lg.Warn().
   559  				Bool(logging.KeySuspicious, true).
   560  				Msg(MeshLogIntervalWarnMsg)
   561  			continue
   562  		}
   563  		lg.Debug().Msg(MeshLogIntervalMsg)
   564  	}
   565  }