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

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/libp2p/go-libp2p/core/peer"
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/module"
    12  	herocache "github.com/onflow/flow-go/module/mempool/herocache/backdata"
    13  	"github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool"
    14  	"github.com/onflow/flow-go/module/mempool/stdmap"
    15  	"github.com/onflow/flow-go/network/p2p/scoring"
    16  )
    17  
    18  type recordEntityFactory func(identifier flow.Identifier) ClusterPrefixedMessagesReceivedRecord
    19  
    20  type RecordCacheConfig struct {
    21  	sizeLimit uint32
    22  	logger    zerolog.Logger
    23  	collector module.HeroCacheMetrics
    24  	// recordDecay decay factor used by the cache to perform geometric decay on gauge values.
    25  	recordDecay float64
    26  }
    27  
    28  // RecordCache is a cache that stores ClusterPrefixedMessagesReceivedRecord by peer node ID. Each record
    29  // contains a float64 Gauge field that indicates the current approximate number cluster prefixed control messages that were allowed to bypass
    30  // validation due to some error that will prevent the message from being validated.
    31  // Each record contains a float64 Gauge field that is decayed overtime back to 0. This ensures that nodes that fall
    32  // behind in the protocol can catch up.
    33  type RecordCache struct {
    34  	// recordEntityFactory is a factory function that creates a new ClusterPrefixedMessagesReceivedRecord.
    35  	recordEntityFactory recordEntityFactory
    36  	// c is the underlying cache.
    37  	c *stdmap.Backend
    38  	// decayFunc decay func used by the cache to perform decay on gauges.
    39  	decayFunc decayFunc
    40  }
    41  
    42  // NewRecordCache creates a new *RecordCache.
    43  // Args:
    44  // - config: record cache config.
    45  // - recordEntityFactory: a factory function that creates a new spam record.
    46  // Returns:
    47  // - *RecordCache, the created cache.
    48  // Note that this cache is supposed to keep the cluster prefix control messages received record for the authorized (staked) nodes. Since the number of such nodes is
    49  // expected to be small, we do not eject any records from the cache. The cache size must be large enough to hold all
    50  // the records of the authorized nodes. Also, this cache is keeping at most one record per peer id, so the
    51  // size of the cache must be at least the number of authorized nodes.
    52  func NewRecordCache(config *RecordCacheConfig, recordEntityFactory recordEntityFactory) (*RecordCache, error) {
    53  	backData := herocache.NewCache(config.sizeLimit,
    54  		herocache.DefaultOversizeFactor,
    55  		heropool.LRUEjection,
    56  		config.logger.With().Str("mempool", "gossipsub=cluster-prefix-control-messages-received-records").Logger(),
    57  		config.collector)
    58  	return &RecordCache{
    59  		recordEntityFactory: recordEntityFactory,
    60  		decayFunc:           defaultDecayFunction(config.recordDecay),
    61  		c:                   stdmap.NewBackend(stdmap.WithBackData(backData)),
    62  	}, nil
    63  }
    64  
    65  // ReceivedClusterPrefixedMessage applies an adjustment that increments the number of cluster prefixed control messages received by a peer.
    66  // Returns number of cluster prefix control messages received after the adjustment. The record is initialized before
    67  // the adjustment func is applied that will increment the Gauge.
    68  // Args:
    69  // - pid: the peer ID of the sender of the control message.
    70  // Returns:
    71  //   - The cluster prefix control messages received gauge value after the adjustment.
    72  //   - exception only in cases of internal data inconsistency or bugs. No errors are expected.
    73  func (r *RecordCache) ReceivedClusterPrefixedMessage(pid peer.ID) (float64, error) {
    74  	var err error
    75  	adjustFunc := func(entity flow.Entity) flow.Entity {
    76  		entity, err = r.decayAdjustment(entity) // first decay the record
    77  		if err != nil {
    78  			return entity
    79  		}
    80  		return r.incrementAdjustment(entity) // then increment the record
    81  	}
    82  	entityID := r.MakeId(pid)
    83  	adjustedEntity, adjusted := r.c.AdjustWithInit(entityID, adjustFunc, func() flow.Entity {
    84  		return r.recordEntityFactory(entityID)
    85  	})
    86  
    87  	if err != nil {
    88  		return 0, fmt.Errorf("unexpected error while applying decay and increment adjustments for peer %s: %w", pid, err)
    89  	}
    90  
    91  	if !adjusted {
    92  		return 0, fmt.Errorf("adjustment failed for peer %s", pid)
    93  	}
    94  
    95  	record := mustBeClusterPrefixedMessageReceivedRecordEntity(adjustedEntity)
    96  
    97  	return record.Gauge, nil
    98  }
    99  
   100  // GetWithInit returns the current number of cluster prefixed control messages received from a peer.
   101  // The record is initialized before the count is returned.
   102  // Before the control messages received gauge value is returned it is decayed using the configured decay function.
   103  // Returns the record and true if the record exists, nil and false otherwise.
   104  // Args:
   105  // - pid: the peer ID of the sender of the control message.
   106  // Returns:
   107  // - The cluster prefixed control messages received gauge value after the decay and true if the record exists, 0 and false otherwise.
   108  // No errors are expected during normal operation.
   109  func (r *RecordCache) GetWithInit(pid peer.ID) (float64, bool, error) {
   110  	var err error
   111  	adjustLogic := func(entity flow.Entity) flow.Entity {
   112  		// perform decay on gauge value
   113  		entity, err = r.decayAdjustment(entity)
   114  		return entity
   115  	}
   116  	entityID := r.MakeId(pid)
   117  	adjustedEntity, adjusted := r.c.AdjustWithInit(entityID, adjustLogic, func() flow.Entity {
   118  		return r.recordEntityFactory(entityID)
   119  	})
   120  	if err != nil {
   121  		return 0, false, fmt.Errorf("unexpected error while applying decay adjustment for peer %s: %w", pid, err)
   122  	}
   123  	if !adjusted {
   124  		return 0, false, fmt.Errorf("decay adjustment failed for peer %s", pid)
   125  	}
   126  
   127  	record := mustBeClusterPrefixedMessageReceivedRecordEntity(adjustedEntity)
   128  
   129  	return record.Gauge, true, nil
   130  }
   131  
   132  // Remove removes the record of the given peer id from the cache.
   133  // Returns true if the record is removed, false otherwise (i.e., the record does not exist).
   134  // Args:
   135  // - pid: the peer ID of the sender of the control message.
   136  // Returns:
   137  // - true if the record is removed, false otherwise (i.e., the record does not exist).
   138  func (r *RecordCache) Remove(pid peer.ID) bool {
   139  	return r.c.Remove(r.MakeId(pid))
   140  }
   141  
   142  // NodeIDs returns the list of identities of the nodes that have a spam record in the cache.
   143  func (r *RecordCache) NodeIDs() []flow.Identifier {
   144  	return flow.GetIDs(r.c.All())
   145  }
   146  
   147  // Size returns the number of records in the cache.
   148  func (r *RecordCache) Size() uint {
   149  	return r.c.Size()
   150  }
   151  
   152  // MakeId is a helper function for creating the id field of the duplicateMessagesCounterEntity by hashing the peerID.
   153  // Returns:
   154  // - the hash of the peerID as a flow.Identifier.
   155  func (r *RecordCache) MakeId(peerID peer.ID) flow.Identifier {
   156  	return flow.MakeID([]byte(peerID))
   157  }
   158  
   159  func (r *RecordCache) incrementAdjustment(entity flow.Entity) flow.Entity {
   160  	record, ok := entity.(ClusterPrefixedMessagesReceivedRecord)
   161  	if !ok {
   162  		// sanity check
   163  		// This should never happen, because the cache only contains ClusterPrefixedMessagesReceivedRecord entities.
   164  		panic(fmt.Sprintf("invalid entity type, expected ClusterPrefixedMessagesReceivedRecord type, got: %T", entity))
   165  	}
   166  	record.Gauge++
   167  	record.lastUpdated = time.Now()
   168  	// Return the adjusted record.
   169  	return record
   170  }
   171  
   172  // All errors returned from this function are unexpected and irrecoverable.
   173  func (r *RecordCache) decayAdjustment(entity flow.Entity) (flow.Entity, error) {
   174  	record, ok := entity.(ClusterPrefixedMessagesReceivedRecord)
   175  	if !ok {
   176  		// sanity check
   177  		// This should never happen, because the cache only contains ClusterPrefixedMessagesReceivedRecord entities.
   178  		panic(fmt.Sprintf("invalid entity type, expected ClusterPrefixedMessagesReceivedRecord type, got: %T", entity))
   179  	}
   180  	var err error
   181  	record, err = r.decayFunc(record)
   182  	if err != nil {
   183  		return record, err
   184  	}
   185  	record.lastUpdated = time.Now()
   186  	// Return the adjusted record.
   187  	return record, nil
   188  }
   189  
   190  // decayFunc the callback used to apply a decay method to the record.
   191  // All errors returned from this callback are unexpected and irrecoverable.
   192  type decayFunc func(recordEntity ClusterPrefixedMessagesReceivedRecord) (ClusterPrefixedMessagesReceivedRecord, error)
   193  
   194  // defaultDecayFunction is the default decay function that is used to decay the cluster prefixed control message received gauge of a peer.
   195  // All errors returned are unexpected and irrecoverable.
   196  func defaultDecayFunction(decay float64) decayFunc {
   197  	return func(recordEntity ClusterPrefixedMessagesReceivedRecord) (ClusterPrefixedMessagesReceivedRecord, error) {
   198  		received := recordEntity.Gauge
   199  		if received == 0 {
   200  			return recordEntity, nil
   201  		}
   202  
   203  		decayedVal, err := scoring.GeometricDecay(received, decay, recordEntity.lastUpdated)
   204  		if err != nil {
   205  			return recordEntity, fmt.Errorf("could not decay cluster prefixed control messages received gauge: %w", err)
   206  		}
   207  		recordEntity.Gauge = decayedVal
   208  		return recordEntity, nil
   209  	}
   210  }
   211  
   212  // mustBeClusterPrefixedMessageReceivedRecordEntity is a helper function for type assertion of the flow.Entity to ClusterPrefixedMessagesReceivedRecord.
   213  // It panics if the type assertion fails.
   214  // Args:
   215  // - entity: the flow.Entity to be type asserted.
   216  // Returns:
   217  // - the ClusterPrefixedMessagesReceivedRecord entity.
   218  func mustBeClusterPrefixedMessageReceivedRecordEntity(entity flow.Entity) ClusterPrefixedMessagesReceivedRecord {
   219  	c, ok := entity.(ClusterPrefixedMessagesReceivedRecord)
   220  	if !ok {
   221  		// sanity check
   222  		// This should never happen, because the cache only contains ClusterPrefixedMessagesReceivedRecord entities.
   223  		panic(fmt.Sprintf("invalid entity type, expected ClusterPrefixedMessagesReceivedRecord type, got: %T", entity))
   224  	}
   225  	return c
   226  }