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 }