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

     1  package unicastcache
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/libp2p/go-libp2p/core/peer"
     7  	"github.com/rs/zerolog"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module"
    11  	herocache "github.com/onflow/flow-go/module/mempool/herocache/backdata"
    12  	"github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool"
    13  	"github.com/onflow/flow-go/module/mempool/stdmap"
    14  	"github.com/onflow/flow-go/network/p2p/unicast"
    15  )
    16  
    17  type UnicastConfigCache struct {
    18  	peerCache  *stdmap.Backend
    19  	cfgFactory func() unicast.Config // factory function that creates a new unicast config.
    20  }
    21  
    22  var _ unicast.ConfigCache = (*UnicastConfigCache)(nil)
    23  
    24  // NewUnicastConfigCache creates a new UnicastConfigCache.
    25  // Args:
    26  // - size: the maximum number of unicast configs that the cache can hold.
    27  // - logger: the logger used by the cache.
    28  // - collector: the metrics collector used by the cache.
    29  // - cfgFactory: a factory function that creates a new unicast config.
    30  // Returns:
    31  // - *UnicastConfigCache, the created cache.
    32  // Note that the cache is supposed to keep the unicast config for all types of nodes. Since the number of such nodes is
    33  // expected to be small, size must be large enough to hold all the unicast configs of the authorized nodes.
    34  // To avoid any crash-failure, the cache is configured to eject the least recently used configs when the cache is full.
    35  // Hence, we recommend setting the size to a large value to minimize the ejections.
    36  func NewUnicastConfigCache(
    37  	size uint32,
    38  	logger zerolog.Logger,
    39  	collector module.HeroCacheMetrics,
    40  	cfgFactory func() unicast.Config,
    41  ) *UnicastConfigCache {
    42  	return &UnicastConfigCache{
    43  		peerCache: stdmap.NewBackend(stdmap.WithBackData(herocache.NewCache(size,
    44  			herocache.DefaultOversizeFactor,
    45  			heropool.LRUEjection,
    46  			logger.With().Str("module", "unicast-config-cache").Logger(),
    47  			collector))),
    48  		cfgFactory: cfgFactory,
    49  	}
    50  }
    51  
    52  // AdjustWithInit applies the given adjust function to the unicast config of the given peer ID, and stores the adjusted config in the cache.
    53  // It returns an error if the adjustFunc returns an error.
    54  // Note that if the Adjust is called when the config does not exist, the config is initialized and the
    55  // adjust function is applied to the initialized config again. In this case, the adjust function should not return an error.
    56  // Args:
    57  // - peerID: the peer id of the unicast config.
    58  // - adjustFunc: the function that adjusts the unicast config.
    59  // Returns:
    60  //   - error any returned error should be considered as an irrecoverable error and indicates a bug.
    61  func (d *UnicastConfigCache) AdjustWithInit(peerID peer.ID, adjustFunc unicast.UnicastConfigAdjustFunc) (*unicast.Config, error) {
    62  	entityId := entityIdOf(peerID)
    63  	var rErr error
    64  	// wraps external adjust function to adjust the unicast config.
    65  	wrapAdjustFunc := func(entity flow.Entity) flow.Entity {
    66  		cfgEntity, ok := entity.(UnicastConfigEntity)
    67  		if !ok {
    68  			// sanity check
    69  			// This should never happen, because the cache only contains UnicastConfigEntity entities.
    70  			panic(fmt.Sprintf("invalid entity type, expected UnicastConfigEntity type, got: %T", entity))
    71  		}
    72  
    73  		// adjust the unicast config.
    74  		adjustedCfg, err := adjustFunc(cfgEntity.Config)
    75  		if err != nil {
    76  			rErr = fmt.Errorf("adjust function failed: %w", err)
    77  			return entity // returns the original entity (reverse the adjustment).
    78  		}
    79  
    80  		// Return the adjusted config.
    81  		cfgEntity.Config = adjustedCfg
    82  		return cfgEntity
    83  	}
    84  
    85  	initFunc := func() flow.Entity {
    86  		return UnicastConfigEntity{
    87  			PeerId:   peerID,
    88  			Config:   d.cfgFactory(),
    89  			EntityId: entityId,
    90  		}
    91  	}
    92  
    93  	adjustedEntity, adjusted := d.peerCache.AdjustWithInit(entityId, wrapAdjustFunc, initFunc)
    94  	if rErr != nil {
    95  		return nil, fmt.Errorf("adjust operation aborted with an error: %w", rErr)
    96  	}
    97  
    98  	if !adjusted {
    99  		return nil, fmt.Errorf("adjust operation aborted, entity not found")
   100  	}
   101  
   102  	return &unicast.Config{
   103  		StreamCreationRetryAttemptBudget: adjustedEntity.(UnicastConfigEntity).StreamCreationRetryAttemptBudget,
   104  		ConsecutiveSuccessfulStream:      adjustedEntity.(UnicastConfigEntity).ConsecutiveSuccessfulStream,
   105  	}, nil
   106  }
   107  
   108  // GetWithInit returns the unicast config for the given peer id. If the config does not exist, it creates a new config
   109  // using the factory function and stores it in the cache.
   110  // Args:
   111  // - peerID: the peer id of the unicast config.
   112  // Returns:
   113  //   - *Config, the unicast config for the given peer id.
   114  //   - error if the factory function returns an error. Any error should be treated as an irrecoverable error and indicates a bug.
   115  func (d *UnicastConfigCache) GetWithInit(peerID peer.ID) (*unicast.Config, error) {
   116  	// ensuring that the init-and-get operation is atomic.
   117  	entityId := entityIdOf(peerID)
   118  	initFunc := func() flow.Entity {
   119  		return UnicastConfigEntity{
   120  			PeerId:   peerID,
   121  			Config:   d.cfgFactory(),
   122  			EntityId: entityId,
   123  		}
   124  	}
   125  	entity, ok := d.peerCache.GetWithInit(entityId, initFunc)
   126  	if !ok {
   127  		return nil, fmt.Errorf("get or init for unicast config for peer %s failed", peerID)
   128  	}
   129  	cfg, ok := entity.(UnicastConfigEntity)
   130  	if !ok {
   131  		// sanity check
   132  		// This should never happen, because the cache only contains UnicastConfigEntity entities.
   133  		panic(fmt.Sprintf("invalid entity type, expected UnicastConfigEntity type, got: %T", entity))
   134  	}
   135  
   136  	// return a copy of the config (we do not want the caller to modify the config).
   137  	return &unicast.Config{
   138  		StreamCreationRetryAttemptBudget: cfg.StreamCreationRetryAttemptBudget,
   139  		ConsecutiveSuccessfulStream:      cfg.ConsecutiveSuccessfulStream,
   140  	}, nil
   141  }
   142  
   143  // Size returns the number of unicast configs in the cache.
   144  func (d *UnicastConfigCache) Size() uint {
   145  	return d.peerCache.Size()
   146  }