gossip: add CrdsFilter
This commit is contained in:
parent
6bad7ba959
commit
b4c108cc5c
|
@ -55,7 +55,15 @@ func BloomNumKeys(m, n float64) float64 {
|
||||||
return math.Max(1, math.Round((m/n)*math.Log(2)))
|
return math.Max(1, math.Round((m/n)*math.Log(2)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bloom) Pos(key *[32]byte, k uint64) uint64 {
|
func BloomMaxItems(m, p, k float64) float64 {
|
||||||
|
return math.Ceil(m / (-k / math.Log(1-math.Exp(math.Log(p)/k))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BloomMaskBits(numItems, maxItems float64) uint32 {
|
||||||
|
return uint32(math.Max(math.Ceil(math.Log2(numItems/maxItems)), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bloom) Pos(key *Hash, k uint64) uint64 {
|
||||||
return FNV1a(key[:], k) % b.Bits.Len
|
return FNV1a(key[:], k) % b.Bits.Len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +75,7 @@ func (b *Bloom) Clear() {
|
||||||
b.NumBitsSet = 0
|
b.NumBitsSet = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bloom) Add(key *[32]byte) {
|
func (b *Bloom) Add(key *Hash) {
|
||||||
for _, k := range b.Keys {
|
for _, k := range b.Keys {
|
||||||
pos := b.Pos(key, k)
|
pos := b.Pos(key, k)
|
||||||
if !b.Bits.Get(pos) {
|
if !b.Bits.Get(pos) {
|
||||||
|
@ -77,7 +85,7 @@ func (b *Bloom) Add(key *[32]byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bloom) Contains(key *[32]byte) bool {
|
func (b *Bloom) Contains(key *Hash) bool {
|
||||||
for _, k := range b.Keys {
|
for _, k := range b.Keys {
|
||||||
if !b.Bits.Get(b.Pos(key, k)) {
|
if !b.Bits.Get(b.Pos(key, k)) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestBloom_AddContains(t *testing.T) {
|
||||||
// known keys to avoid false positives in the test
|
// known keys to avoid false positives in the test
|
||||||
bloom.Keys = []uint64{0, 1, 2, 3}
|
bloom.Keys = []uint64{0, 1, 2, 3}
|
||||||
|
|
||||||
var key [32]byte
|
var key Hash
|
||||||
|
|
||||||
key = sha256.Sum256([]byte("hello"))
|
key = sha256.Sum256([]byte("hello"))
|
||||||
assert.False(t, bloom.Contains(&key))
|
assert.False(t, bloom.Contains(&key))
|
||||||
|
@ -85,3 +85,7 @@ func TestBloom_Randomness(t *testing.T) {
|
||||||
|
|
||||||
assert.NotEqual(t, b1.Keys, b2.Keys)
|
assert.NotEqual(t, b1.Keys, b2.Keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBloom_MaxItems(t *testing.T) {
|
||||||
|
assert.Equal(t, float64(9), BloomMaxItems(80, 0.01, 8))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package gossip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CrdsBloomP is bloom filter 'p' parameter (probability)
|
||||||
|
const CrdsBloomP = 0.1
|
||||||
|
|
||||||
|
func (f *CrdsFilter) Contains(item *Hash) bool {
|
||||||
|
if !f.TestMask(item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return f.Filter.Contains(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CrdsFilter) TestMask(item *Hash) bool {
|
||||||
|
ones := uint64(math.MaxUint64) >> f.MaskBits
|
||||||
|
bits := binary.LittleEndian.Uint64(item[:8]) | ones
|
||||||
|
return bits == f.Mask
|
||||||
|
}
|
||||||
|
|
||||||
|
type CrdsFilterSet []CrdsFilter
|
||||||
|
|
||||||
|
func NewCrdsFilterSet(numItems, maxBytes uint64) CrdsFilterSet {
|
||||||
|
maxBits := maxBytes * 8
|
||||||
|
maxItems := BloomMaxItems(float64(maxBits), CrdsBloomP, 8)
|
||||||
|
maskBits := BloomMaskBits(float64(numItems), maxItems)
|
||||||
|
filters := make([]CrdsFilter, 1<<maskBits)
|
||||||
|
for i := uint64(0); i < uint64(len(filters)); i++ {
|
||||||
|
bloom := NewBloomRandom(uint64(maxItems), CrdsBloomP, maxBits)
|
||||||
|
seed := i << (64 - maskBits)
|
||||||
|
filters[i] = CrdsFilter{
|
||||||
|
Filter: *bloom,
|
||||||
|
Mask: seed | (math.MaxUint64 >> maskBits),
|
||||||
|
MaskBits: maskBits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CrdsFilterSet) Add(h Hash) {
|
||||||
|
index := binary.LittleEndian.Uint64(h[:8])
|
||||||
|
index >>= 64 - c[0].MaskBits
|
||||||
|
c[index].Filter.Add(&h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CrdsValue) Sign(identity ed25519.PrivateKey) error {
|
||||||
|
// Write pubkey into data field
|
||||||
|
pubkey := ed25519.PublicKey(c.Data.Pubkey()[:])
|
||||||
|
copy(
|
||||||
|
pubkey[:ed25519.PublicKeySize],
|
||||||
|
identity.Public().(ed25519.PublicKey)[:ed25519.PublicKeySize],
|
||||||
|
)
|
||||||
|
|
||||||
|
msg, err := c.Data.BincodeSerialize()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sig := ed25519.Sign(identity, msg)
|
||||||
|
copy(c.Signature[:ed25519.SignatureSize], sig[:ed25519.SignatureSize])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CrdsValue) VerifySignature() bool {
|
||||||
|
msg, err := c.Data.BincodeSerialize()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pubkey := ed25519.PublicKey(c.Data.Pubkey()[:])
|
||||||
|
return ed25519.Verify(pubkey, msg, c.Signature[:])
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package gossip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCrdsFilterSet(t *testing.T) {
|
||||||
|
filters := NewCrdsFilterSet(55345017, 4098)
|
||||||
|
assert.Equal(t, 16384, len(filters))
|
||||||
|
maskBits := filters[0].MaskBits
|
||||||
|
rightShift := 64 - maskBits
|
||||||
|
ones := uint64(math.MaxUint64) >> maskBits
|
||||||
|
for i, filter := range filters {
|
||||||
|
assert.Equal(t, maskBits, filter.MaskBits)
|
||||||
|
assert.Equal(t, uint64(i), filter.Mask>>rightShift)
|
||||||
|
assert.Equal(t, ones, ones&filter.Mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCrdsFilterSet_Add(t *testing.T) {
|
||||||
|
filters := NewCrdsFilterSet(9672788, 8196)
|
||||||
|
hashValues := make([]Hash, 1024)
|
||||||
|
for i := range hashValues {
|
||||||
|
rand.Read(hashValues[i][:])
|
||||||
|
filters.Add(hashValues[i])
|
||||||
|
}
|
||||||
|
for _, hashValue := range hashValues {
|
||||||
|
numHits := uint(0)
|
||||||
|
falsePositives := uint(0)
|
||||||
|
for _, filter := range filters {
|
||||||
|
if filter.TestMask(&hashValue) {
|
||||||
|
numHits++
|
||||||
|
assert.True(t, filter.Contains(&hashValue))
|
||||||
|
assert.True(t, filter.Filter.Contains(&hashValue))
|
||||||
|
} else if filter.Filter.Contains(&hashValue) {
|
||||||
|
falsePositives++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, numHits, uint(1))
|
||||||
|
assert.Less(t, falsePositives, uint(5))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue