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

     1  package internal
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    10  	pb "github.com/libp2p/go-libp2p-pubsub/pb"
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/onflow/flow-go/engine/common/worker"
    14  	"github.com/onflow/flow-go/module"
    15  	"github.com/onflow/flow-go/module/component"
    16  	"github.com/onflow/flow-go/module/mempool/queue"
    17  	p2pmsg "github.com/onflow/flow-go/network/p2p/message"
    18  )
    19  
    20  const (
    21  	iHaveRPCTrackedLog = "ihave rpc tracked successfully"
    22  )
    23  
    24  // trackableRPC is an internal data structure for "temporarily" storing *pubsub.RPC sent in the queue before they are processed
    25  // by the *RPCSentTracker.
    26  type trackableRPC struct {
    27  	// Nonce prevents deduplication in the hero store
    28  	Nonce []byte
    29  	rpc   *pubsub.RPC
    30  }
    31  
    32  // lastHighestIHaveRPCSize tracks the last highest rpc control message size the time stamp it was last updated.
    33  type lastHighestIHaveRPCSize struct {
    34  	sync.RWMutex
    35  	lastSize   int64
    36  	lastUpdate time.Time
    37  }
    38  
    39  // RPCSentTracker tracks RPC messages and the size of the last largest iHave rpc control message sent.
    40  type RPCSentTracker struct {
    41  	component.Component
    42  	*lastHighestIHaveRPCSize
    43  	logger                               zerolog.Logger
    44  	cache                                *rpcSentCache
    45  	workerPool                           *worker.Pool[trackableRPC]
    46  	lastHighestIHaveRPCSizeResetInterval time.Duration
    47  }
    48  
    49  // RPCSentTrackerConfig configuration for the RPCSentTracker.
    50  type RPCSentTrackerConfig struct {
    51  	Logger zerolog.Logger
    52  	//RPCSentCacheSize size of the *rpcSentCache cache.
    53  	RPCSentCacheSize uint32
    54  	// RPCSentCacheCollector metrics collector for the *rpcSentCache cache.
    55  	RPCSentCacheCollector module.HeroCacheMetrics
    56  	// WorkerQueueCacheCollector metrics factory for the worker pool.
    57  	WorkerQueueCacheCollector module.HeroCacheMetrics
    58  	// WorkerQueueCacheSize the worker pool herostore cache size.
    59  	WorkerQueueCacheSize uint32
    60  	// NumOfWorkers number of workers in the worker pool.
    61  	NumOfWorkers int
    62  	// LastHighestIhavesSentResetInterval the refresh interval to reset the lastHighestIHaveRPCSize.
    63  	LastHighestIhavesSentResetInterval time.Duration
    64  }
    65  
    66  // NewRPCSentTracker returns a new *NewRPCSentTracker.
    67  func NewRPCSentTracker(config *RPCSentTrackerConfig) *RPCSentTracker {
    68  	cacheConfig := &rpcCtrlMsgSentCacheConfig{
    69  		sizeLimit: config.RPCSentCacheSize,
    70  		logger:    config.Logger,
    71  		collector: config.RPCSentCacheCollector,
    72  	}
    73  
    74  	store := queue.NewHeroStore(
    75  		config.WorkerQueueCacheSize,
    76  		config.Logger,
    77  		config.WorkerQueueCacheCollector)
    78  
    79  	tracker := &RPCSentTracker{
    80  		logger:                               config.Logger.With().Str("component", "rpc_sent_tracker").Logger(),
    81  		lastHighestIHaveRPCSize:              &lastHighestIHaveRPCSize{sync.RWMutex{}, 0, time.Now()},
    82  		cache:                                newRPCSentCache(cacheConfig),
    83  		lastHighestIHaveRPCSizeResetInterval: config.LastHighestIhavesSentResetInterval,
    84  	}
    85  	tracker.workerPool = worker.NewWorkerPoolBuilder[trackableRPC](
    86  		config.Logger,
    87  		store,
    88  		tracker.rpcSentWorkerLogic).Build()
    89  
    90  	builder := component.NewComponentManagerBuilder()
    91  	for i := 0; i < config.NumOfWorkers; i++ {
    92  		builder.AddWorker(tracker.workerPool.WorkerLogic())
    93  	}
    94  	tracker.Component = builder.Build()
    95  	return tracker
    96  }
    97  
    98  // Track submits the control message to the worker queue for async tracking.
    99  // Args:
   100  // - *pubsub.RPC: the rpc sent.
   101  // All errors returned from this function can be considered benign.
   102  func (t *RPCSentTracker) Track(rpc *pubsub.RPC) error {
   103  	n, err := nonce()
   104  	if err != nil {
   105  		return fmt.Errorf("failed to get track rpc work nonce: %w", err)
   106  	}
   107  
   108  	if ok := t.workerPool.Submit(trackableRPC{Nonce: n, rpc: rpc}); !ok {
   109  		return fmt.Errorf("failed to track RPC could not submit work to worker pool")
   110  	}
   111  	return nil
   112  }
   113  
   114  // rpcSentWorkerLogic tracks control messages sent in *pubsub.RPC.
   115  func (t *RPCSentTracker) rpcSentWorkerLogic(work trackableRPC) error {
   116  	switch {
   117  	case len(work.rpc.GetControl().GetIhave()) > 0:
   118  		iHave := work.rpc.GetControl().GetIhave()
   119  		numOfMessageIdsTracked := t.iHaveRPCSent(iHave)
   120  		lastHighestIHaveCount := t.updateLastHighestIHaveRPCSize(int64(numOfMessageIdsTracked))
   121  		t.logger.Debug().
   122  			Int("num_of_ihaves", len(iHave)).
   123  			Int("num_of_message_ids", numOfMessageIdsTracked).
   124  			Int64("last_highest_ihave_count", lastHighestIHaveCount).
   125  			Msg(iHaveRPCTrackedLog)
   126  	}
   127  	return nil
   128  }
   129  
   130  // updateLastHighestIHaveRPCSize updates the last highest if the provided size is larger than the current last highest or the reset interval has passed.
   131  // Args:
   132  // - size: size that was cached.
   133  // Returns:
   134  // - int64: the last highest size.
   135  func (t *RPCSentTracker) updateLastHighestIHaveRPCSize(size int64) int64 {
   136  	t.Lock()
   137  	defer t.Unlock()
   138  	if t.lastSize < size || time.Since(t.lastUpdate) > t.lastHighestIHaveRPCSizeResetInterval {
   139  		// The last highest ihave RPC size is updated if the new size is larger than the current size, or if the time elapsed since the last update surpasses the reset interval.
   140  		t.lastSize = size
   141  		t.lastUpdate = time.Now()
   142  	}
   143  	return t.lastSize
   144  }
   145  
   146  // iHaveRPCSent caches a unique entity message ID for each message ID included in each rpc iHave control message.
   147  // Args:
   148  // - []*pb.ControlIHave: list of iHave control messages.
   149  // Returns:
   150  // - int: the number of message ids cached by the tracker.
   151  func (t *RPCSentTracker) iHaveRPCSent(iHaves []*pb.ControlIHave) int {
   152  	controlMsgType := p2pmsg.CtrlMsgIHave
   153  	messageIDCount := 0
   154  	for _, iHave := range iHaves {
   155  		messageIDCount += len(iHave.GetMessageIDs())
   156  		for _, messageID := range iHave.GetMessageIDs() {
   157  			t.cache.add(messageID, controlMsgType)
   158  		}
   159  	}
   160  	return messageIDCount
   161  }
   162  
   163  // WasIHaveRPCSent checks if an iHave control message with the provided message ID was sent.
   164  // Args:
   165  // - messageID: the message ID of the iHave RPC.
   166  // Returns:
   167  // - bool: true if the iHave rpc with the provided message ID was sent.
   168  func (t *RPCSentTracker) WasIHaveRPCSent(messageID string) bool {
   169  	return t.cache.has(messageID, p2pmsg.CtrlMsgIHave)
   170  }
   171  
   172  // LastHighestIHaveRPCSize returns the last highest size of iHaves sent in a rpc.
   173  // Returns:
   174  // - int64: the last highest size.
   175  func (t *RPCSentTracker) LastHighestIHaveRPCSize() int64 {
   176  	t.RLock()
   177  	defer t.RUnlock()
   178  	return t.lastSize
   179  }
   180  
   181  // nonce returns random string that is used to store unique items in herocache.
   182  func nonce() ([]byte, error) {
   183  	b := make([]byte, 16)
   184  	_, err := rand.Read(b)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	return b, nil
   189  }