github.com/ava-labs/avalanchego@v1.11.11/network/p2p/gossip/bloom.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package gossip
     5  
     6  import (
     7  	"crypto/rand"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  
    11  	"github.com/ava-labs/avalanchego/ids"
    12  	"github.com/ava-labs/avalanchego/utils/bloom"
    13  )
    14  
    15  // NewBloomFilter returns a new instance of a bloom filter with at least [minTargetElements] elements
    16  // anticipated at any moment, and a false positive probability of [targetFalsePositiveProbability]. If the
    17  // false positive probability exceeds [resetFalsePositiveProbability], the bloom filter will be reset.
    18  //
    19  // Invariant: The returned bloom filter is not safe to reset concurrently with
    20  // other operations. However, it is otherwise safe to access concurrently.
    21  func NewBloomFilter(
    22  	registerer prometheus.Registerer,
    23  	namespace string,
    24  	minTargetElements int,
    25  	targetFalsePositiveProbability,
    26  	resetFalsePositiveProbability float64,
    27  ) (*BloomFilter, error) {
    28  	metrics, err := bloom.NewMetrics(namespace, registerer)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	filter := &BloomFilter{
    33  		minTargetElements:              minTargetElements,
    34  		targetFalsePositiveProbability: targetFalsePositiveProbability,
    35  		resetFalsePositiveProbability:  resetFalsePositiveProbability,
    36  
    37  		metrics: metrics,
    38  	}
    39  	err = resetBloomFilter(
    40  		filter,
    41  		minTargetElements,
    42  		targetFalsePositiveProbability,
    43  		resetFalsePositiveProbability,
    44  	)
    45  	return filter, err
    46  }
    47  
    48  type BloomFilter struct {
    49  	minTargetElements              int
    50  	targetFalsePositiveProbability float64
    51  	resetFalsePositiveProbability  float64
    52  
    53  	metrics *bloom.Metrics
    54  
    55  	maxCount int
    56  	bloom    *bloom.Filter
    57  	// salt is provided to eventually unblock collisions in Bloom. It's possible
    58  	// that conflicting Gossipable items collide in the bloom filter, so a salt
    59  	// is generated to eventually resolve collisions.
    60  	salt ids.ID
    61  }
    62  
    63  func (b *BloomFilter) Add(gossipable Gossipable) {
    64  	h := gossipable.GossipID()
    65  	bloom.Add(b.bloom, h[:], b.salt[:])
    66  	b.metrics.Count.Inc()
    67  }
    68  
    69  func (b *BloomFilter) Has(gossipable Gossipable) bool {
    70  	h := gossipable.GossipID()
    71  	return bloom.Contains(b.bloom, h[:], b.salt[:])
    72  }
    73  
    74  func (b *BloomFilter) Marshal() ([]byte, []byte) {
    75  	bloomBytes := b.bloom.Marshal()
    76  	// salt must be copied here to ensure the bytes aren't overwritten if salt
    77  	// is later modified.
    78  	salt := b.salt
    79  	return bloomBytes, salt[:]
    80  }
    81  
    82  // ResetBloomFilterIfNeeded resets a bloom filter if it breaches [targetFalsePositiveProbability].
    83  //
    84  // If [targetElements] exceeds [minTargetElements], the size of the bloom filter will grow to maintain
    85  // the same [targetFalsePositiveProbability].
    86  //
    87  // Returns true if the bloom filter was reset.
    88  func ResetBloomFilterIfNeeded(
    89  	bloomFilter *BloomFilter,
    90  	targetElements int,
    91  ) (bool, error) {
    92  	if bloomFilter.bloom.Count() <= bloomFilter.maxCount {
    93  		return false, nil
    94  	}
    95  
    96  	targetElements = max(bloomFilter.minTargetElements, targetElements)
    97  	err := resetBloomFilter(
    98  		bloomFilter,
    99  		targetElements,
   100  		bloomFilter.targetFalsePositiveProbability,
   101  		bloomFilter.resetFalsePositiveProbability,
   102  	)
   103  	return err == nil, err
   104  }
   105  
   106  func resetBloomFilter(
   107  	bloomFilter *BloomFilter,
   108  	targetElements int,
   109  	targetFalsePositiveProbability,
   110  	resetFalsePositiveProbability float64,
   111  ) error {
   112  	numHashes, numEntries := bloom.OptimalParameters(
   113  		targetElements,
   114  		targetFalsePositiveProbability,
   115  	)
   116  	newBloom, err := bloom.New(numHashes, numEntries)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	var newSalt ids.ID
   121  	if _, err := rand.Read(newSalt[:]); err != nil {
   122  		return err
   123  	}
   124  
   125  	bloomFilter.maxCount = bloom.EstimateCount(numHashes, numEntries, resetFalsePositiveProbability)
   126  	bloomFilter.bloom = newBloom
   127  	bloomFilter.salt = newSalt
   128  
   129  	bloomFilter.metrics.Reset(newBloom, bloomFilter.maxCount)
   130  	return nil
   131  }