github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/discovery/authcache.go (about)

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