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 }