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