github.com/anjalikarhana/fabric@v2.1.1+incompatible/discovery/authcache.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package discovery 8 9 import ( 10 "encoding/asn1" 11 "encoding/hex" 12 "sync" 13 14 "github.com/hyperledger/fabric/common/util" 15 "github.com/hyperledger/fabric/protoutil" 16 "github.com/pkg/errors" 17 ) 18 19 const ( 20 defaultMaxCacheSize = 1000 21 defaultRetentionRatio = 0.75 22 ) 23 24 var ( 25 // asBytes is the function that is used to marshal protoutil.SignedData to bytes 26 asBytes = asn1.Marshal 27 ) 28 29 type acSupport interface { 30 // Eligible returns whether the given peer is eligible for receiving 31 // service from the discovery service for a given channel 32 EligibleForService(channel string, data protoutil.SignedData) error 33 34 // ConfigSequence returns the configuration sequence of the given channel 35 ConfigSequence(channel string) uint64 36 } 37 38 type authCacheConfig struct { 39 enabled bool 40 // maxCacheSize is the maximum size of the cache, after which 41 // a purge takes place 42 maxCacheSize int 43 // purgeRetentionRatio is the % of entries that remain in the cache 44 // after the cache is purged due to overpopulation 45 purgeRetentionRatio float64 46 } 47 48 // authCache defines an interface that authenticates a request in a channel context, 49 // and does memoization of invocations 50 type authCache struct { 51 credentialCache map[string]*accessCache 52 acSupport 53 sync.RWMutex 54 conf authCacheConfig 55 } 56 57 func newAuthCache(s acSupport, conf authCacheConfig) *authCache { 58 return &authCache{ 59 acSupport: s, 60 credentialCache: make(map[string]*accessCache), 61 conf: conf, 62 } 63 } 64 65 // Eligible returns whether the given peer is eligible for receiving 66 // service from the discovery service for a given channel 67 func (ac *authCache) EligibleForService(channel string, data protoutil.SignedData) error { 68 if !ac.conf.enabled { 69 return ac.acSupport.EligibleForService(channel, data) 70 } 71 // Check whether we already have a cache for this channel 72 ac.RLock() 73 cache := ac.credentialCache[channel] 74 ac.RUnlock() 75 if cache == nil { 76 // Cache for given channel wasn't found, so create a new one 77 ac.Lock() 78 cache = ac.newAccessCache(channel) 79 // And store the cache instance. 80 ac.credentialCache[channel] = cache 81 ac.Unlock() 82 } 83 return cache.EligibleForService(data) 84 } 85 86 type accessCache struct { 87 sync.RWMutex 88 channel string 89 ac *authCache 90 lastSequence uint64 91 entries map[string]error 92 } 93 94 func (ac *authCache) newAccessCache(channel string) *accessCache { 95 return &accessCache{ 96 channel: channel, 97 ac: ac, 98 entries: make(map[string]error), 99 } 100 } 101 102 func (cache *accessCache) EligibleForService(data protoutil.SignedData) error { 103 key, err := signedDataToKey(data) 104 if err != nil { 105 logger.Warningf("Failed computing key of signed data: +%v", err) 106 return errors.Wrap(err, "failed computing key of signed data") 107 } 108 currSeq := cache.ac.acSupport.ConfigSequence(cache.channel) 109 if cache.isValid(currSeq) { 110 foundInCache, isEligibleErr := cache.lookup(key) 111 if foundInCache { 112 return isEligibleErr 113 } 114 } else { 115 cache.configChange(currSeq) 116 } 117 118 // Make sure the cache doesn't overpopulate. 119 // It might happen that it overgrows the maximum size due to concurrent 120 // goroutines waiting on the lock above, but that's acceptable. 121 cache.purgeEntriesIfNeeded() 122 123 // Compute the eligibility of the client for the service 124 err = cache.ac.acSupport.EligibleForService(cache.channel, data) 125 cache.Lock() 126 defer cache.Unlock() 127 // Check if the sequence hasn't changed since last time 128 if currSeq != cache.ac.acSupport.ConfigSequence(cache.channel) { 129 // The sequence at which we computed the eligibility might have changed, 130 // so we can't put it into the cache because a more fresh computation result 131 // might already be present in the cache by now, and we don't want to override it 132 // with a stale computation result, so just return the result. 133 return err 134 } 135 // Else, the eligibility of the client has been computed under the latest sequence, 136 // so store the computation result in the cache 137 cache.entries[key] = err 138 return err 139 } 140 141 func (cache *accessCache) isPurgeNeeded() bool { 142 cache.RLock() 143 defer cache.RUnlock() 144 return len(cache.entries)+1 > cache.ac.conf.maxCacheSize 145 } 146 147 func (cache *accessCache) purgeEntriesIfNeeded() { 148 if !cache.isPurgeNeeded() { 149 return 150 } 151 152 cache.Lock() 153 defer cache.Unlock() 154 155 maxCacheSize := cache.ac.conf.maxCacheSize 156 purgeRatio := cache.ac.conf.purgeRetentionRatio 157 entries2evict := maxCacheSize - int(purgeRatio*float64(maxCacheSize)) 158 159 for key := range cache.entries { 160 if entries2evict == 0 { 161 return 162 } 163 entries2evict-- 164 delete(cache.entries, key) 165 } 166 } 167 168 func (cache *accessCache) isValid(currSeq uint64) bool { 169 cache.RLock() 170 defer cache.RUnlock() 171 return currSeq == cache.lastSequence 172 } 173 174 func (cache *accessCache) configChange(currSeq uint64) { 175 cache.Lock() 176 defer cache.Unlock() 177 cache.lastSequence = currSeq 178 // Invalidate entries 179 cache.entries = make(map[string]error) 180 } 181 182 func (cache *accessCache) lookup(key string) (cacheHit bool, lookupResult error) { 183 cache.RLock() 184 defer cache.RUnlock() 185 186 lookupResult, cacheHit = cache.entries[key] 187 return 188 } 189 190 func signedDataToKey(data protoutil.SignedData) (string, error) { 191 b, err := asBytes(data) 192 if err != nil { 193 return "", errors.Wrap(err, "failed marshaling signed data") 194 } 195 return hex.EncodeToString(util.ComputeSHA256(b)), nil 196 }