Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bloom filter gossip #266

Closed
wants to merge 81 commits into from
Closed
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
76b6dcc
bloom filter gossip
joshua-kim Jun 6, 2023
2df7199
nit
joshua-kim Jul 27, 2023
7b138e3
nit
joshua-kim Jul 31, 2023
74c6d87
nits
joshua-kim Jul 31, 2023
88c2d8e
nits
joshua-kim Aug 1, 2023
2d0aee3
nits
joshua-kim Aug 1, 2023
5926bca
nit
joshua-kim Aug 1, 2023
bc5224b
nit
joshua-kim Aug 1, 2023
642d03c
nit
joshua-kim Aug 1, 2023
9fc69eb
nit
joshua-kim Aug 1, 2023
da10671
public tag
joshua-kim Aug 1, 2023
bd2cb74
remove double pointer
joshua-kim Aug 1, 2023
7a91014
nits
joshua-kim Aug 1, 2023
d31e12d
nit
joshua-kim Aug 1, 2023
e73eb43
nit
joshua-kim Aug 1, 2023
46126c7
nit
joshua-kim Aug 1, 2023
3c45c3f
Update gossip/test_gossip.go
joshua-kim Aug 2, 2023
9720e3c
nit
joshua-kim Aug 1, 2023
020c43f
nit
joshua-kim Aug 3, 2023
4b3ee0a
nit
joshua-kim Aug 4, 2023
f5ee846
nit
joshua-kim Aug 4, 2023
f925c43
nit
joshua-kim Aug 4, 2023
dd8bb71
changes
darioush Aug 7, 2023
16507bd
merge master
darioush Aug 7, 2023
c401a7e
revert
darioush Aug 7, 2023
5aaff37
use consistent version
darioush Aug 7, 2023
fb5b1ac
bump go version
darioush Aug 7, 2023
236e426
fix
darioush Aug 7, 2023
152d450
Merge pull request #295 from ava-labs/bloom-filter-gossip-review
joshua-kim Aug 7, 2023
66bbcac
nit
joshua-kim Aug 8, 2023
8a50023
more unit tests
joshua-kim Aug 8, 2023
a360c28
nit
joshua-kim Aug 8, 2023
088f747
nit
joshua-kim Aug 8, 2023
90f0301
nit
joshua-kim Aug 8, 2023
1fe4b83
more unit tests
joshua-kim Aug 8, 2023
ef521d7
nits
joshua-kim Aug 8, 2023
dc471cc
more unit tests
joshua-kim Aug 8, 2023
45871c6
nits
joshua-kim Aug 9, 2023
a222039
fix ut
joshua-kim Aug 9, 2023
3d1a15e
nit
joshua-kim Aug 9, 2023
74496f1
fix fickle test
joshua-kim Aug 11, 2023
48964aa
remove useless goroutine
joshua-kim Aug 14, 2023
ff9a842
fix bloom params
joshua-kim Aug 15, 2023
4fc0855
fix salt
joshua-kim Aug 15, 2023
1f8a298
nits
joshua-kim Aug 15, 2023
9e98721
increase gossip frequency
joshua-kim Aug 15, 2023
369bd96
nit
joshua-kim Aug 15, 2023
b74f3f0
fix test flake
joshua-kim Aug 15, 2023
957ee8e
nit
joshua-kim Aug 15, 2023
4446f4f
nit
joshua-kim Aug 15, 2023
a4d1c9f
nit
joshua-kim Aug 15, 2023
b06bc7e
nit
joshua-kim Aug 15, 2023
2de3042
comment
joshua-kim Aug 16, 2023
a45b07e
nit
joshua-kim Aug 22, 2023
cc64cdd
drop messages from non-validators
joshua-kim Aug 22, 2023
1b94dc4
nit
joshua-kim Aug 22, 2023
1363616
nit
joshua-kim Aug 28, 2023
9d6675c
Squashed commit of the following:
joshua-kim Aug 28, 2023
732f124
nit
joshua-kim Aug 29, 2023
580b9f0
nit
joshua-kim Aug 30, 2023
9c069ed
nit
joshua-kim Aug 30, 2023
d8aab62
Squashed commit of the following:
joshua-kim Aug 30, 2023
30a9b95
Merge branch 'master' into bloom-filter-gossip
joshua-kim Aug 30, 2023
601cc4d
fix unit tests
joshua-kim Aug 30, 2023
4edcf60
clean diff
joshua-kim Aug 30, 2023
dc3a6d0
clean diff
joshua-kim Aug 30, 2023
a738369
clean diff
joshua-kim Aug 30, 2023
d1485a8
update bloom filter constants
joshua-kim Aug 30, 2023
b1492e5
use proto
joshua-kim Aug 30, 2023
b749986
use 32 byte hash
joshua-kim Aug 30, 2023
0cf3989
Update gossip/gossip.go
joshua-kim Aug 31, 2023
b405041
Update plugin/evm/gossip_mempool.go
joshua-kim Aug 31, 2023
43be5e7
nits
joshua-kim Aug 31, 2023
f79dbc0
update go.mod
joshua-kim Aug 31, 2023
74a65f0
Update peer/network.go
joshua-kim Aug 31, 2023
4031484
use avalanchego logger
joshua-kim Aug 31, 2023
f06e037
fix flakey test
joshua-kim Aug 31, 2023
c031805
fix throttling in unit tests
joshua-kim Sep 1, 2023
f112839
fix throttling in unit tests
joshua-kim Sep 1, 2023
f00b4d5
address race comments
joshua-kim Sep 1, 2023
cfedffe
go get pb
joshua-kim Sep 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/VictoriaMetrics/fastcache v1.10.0
github.com/ava-labs/avalanchego v1.10.9-rc.4
github.com/ava-labs/avalanchego v1.10.10-rc.0
github.com/cespare/cp v0.1.0
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811
github.com/davecgh/go-spew v1.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/ava-labs/avalanchego v1.10.9-rc.4 h1:vtavPfRiF6r1Zc6RV8/arEfVpe9GQsLWHbMfIWkHbMI=
github.com/ava-labs/avalanchego v1.10.9-rc.4/go.mod h1:vTBLl1zK36olfLRA7IUfdbvphWqlkuarIoXxvZTHZVw=
github.com/ava-labs/avalanchego v1.10.10-rc.0 h1:6VjkpwhAJ0tDNJK+UIUD8WIb5VelgH3w61mgk7JAkDQ=
github.com/ava-labs/avalanchego v1.10.10-rc.0/go.mod h1:C8R5uiltpc8MQ62ixxgODR+15mesWF0aAw3H+Qrl9Iw=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down
96 changes: 96 additions & 0 deletions gossip/bloom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package gossip

import (
"encoding/binary"
"hash"
"time"

safemath "github.com/ava-labs/avalanchego/utils/math"
bloomfilter "github.com/holiman/bloomfilter/v2"
"golang.org/x/exp/rand"
)

var _ hash.Hash64 = (*hasher)(nil)

func NewBloomFilter(m uint64, p float64) (*BloomFilter, error) {
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
bloom, err := bloomfilter.NewOptimal(m, p)
if err != nil {
return nil, err
}

bloomFilter := &BloomFilter{
Bloom: bloom,
Salt: randomSalt(),
}
return bloomFilter, nil
}

type BloomFilter struct {
Bloom *bloomfilter.Filter
// Salt is provided to eventually unblock collisions in Bloom. It's possible
// that conflicting Gossipable items collide in the bloom filter, so a salt
// is generated to eventually resolve collisions.
Salt []byte
}

func (b *BloomFilter) Add(gossipable Gossipable) {
salted := hasher{
hash: gossipable.GetHash(),
salt: b.Salt,
}
b.Bloom.Add(salted)
}

func (b *BloomFilter) Has(gossipable Gossipable) bool {
salted := hasher{
hash: gossipable.GetHash(),
salt: b.Salt,
}
return b.Bloom.Contains(salted)
}

// ResetBloomFilterIfNeeded resets a bloom filter if it breaches a ratio of
// filled elements. Returns true if the bloom filter was reset.
func ResetBloomFilterIfNeeded(
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
bloomFilter *BloomFilter,
maxFilledRatio float64,
) bool {
if bloomFilter.Bloom.PreciseFilledRatio() < maxFilledRatio {
return false
}

// it's not possible for this to error assuming that the original
// bloom filter's parameters were valid
fresh, _ := bloomfilter.New(bloomFilter.Bloom.M(), bloomFilter.Bloom.K())
bloomFilter.Bloom = fresh
bloomFilter.Salt = randomSalt()
return true
}

func randomSalt() []byte {
salt := make([]byte, HashLength)
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
r := rand.New(rand.NewSource(uint64(time.Now().Nanosecond())))
_, _ = r.Read(salt)
return salt
}

type hasher struct {
hash.Hash64
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
hash Hash
salt []byte
}

func (h hasher) Sum64() uint64 {
for i := 0; i < safemath.Min(len(h.hash), len(h.salt)); i++ {
h.hash[i] ^= h.salt[i]
}

return binary.BigEndian.Uint64(h.hash[:])
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
}
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved

func (h hasher) Size() int {
return HashLength
}
64 changes: 64 additions & 0 deletions gossip/bloom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package gossip

import (
"testing"

bloomfilter "github.com/holiman/bloomfilter/v2"
"github.com/stretchr/testify/require"
)

func TestBloomFilterRefresh(t *testing.T) {
tests := []struct {
name string
refreshRatio float64
add []*testTx
expected []*testTx
}{
{
name: "no refresh",
refreshRatio: 1,
add: []*testTx{
{hash: Hash{0}},
},
expected: []*testTx{
{hash: Hash{0}},
},
},
{
name: "refresh",
refreshRatio: 0.1,
add: []*testTx{
{hash: Hash{0}},
{hash: Hash{1}},
},
expected: []*testTx{
{hash: Hash{1}},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
b, err := bloomfilter.New(10, 1)
require.NoError(err)
bloom := BloomFilter{
Bloom: b,
}

for _, item := range tt.add {
_ = ResetBloomFilterIfNeeded(&bloom, tt.refreshRatio)
bloom.Add(item)
}

require.Equal(uint64(len(tt.expected)), bloom.Bloom.N())

for _, expected := range tt.expected {
require.True(bloom.Has(expected))
}
})
}
}
123 changes: 123 additions & 0 deletions gossip/gossip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package gossip
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved

import (
"context"
"sync"
"time"

"github.com/ethereum/go-ethereum/log"

"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p"
)

// GossipableAny exists to help create non-nil pointers to a concrete Gossipable
type GossipableAny[T any] interface {
*T
Gossipable
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a wild hack... afaict it does work tho... Is there a ref we can link to for this? Essentially it works because:

  1. The type must be a pointer to something
  2. The type must implement the interface.
  3. A pointer to an interface does not implement the interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you summed up how it works. I can add a ref here.

It's basically a decision of whether we prefer this generics black magic (add complexity to the package) vs if we want to add another interface the caller has to return a pointer to their type (add complexity to the caller). I prefer the former personally but can understand if we feel like this is too evil.

Alternative:

type FooFactory struct{}{}

func (FooFactory) MakePointer() *Foo {
    return &Foo{}
}


type Config struct {
Frequency time.Duration
PollSize int
}

func NewGossiper[T any, U GossipableAny[T]](
config Config,
set Set[U],
client *p2p.Client,
codec codec.Manager,
codecVersion uint16,
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
) *Gossiper[T, U] {
return &Gossiper[T, U]{
config: config,
set: set,
client: client,
codec: codec,
codecVersion: codecVersion,
}
}

type Gossiper[T any, U GossipableAny[T]] struct {
config Config
set Set[U]
client *p2p.Client
codec codec.Manager
codecVersion uint16
}

func (g *Gossiper[T, U]) Gossip(shutdownChan chan struct{}, shutdownWg *sync.WaitGroup) {
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
gossipTicker := time.NewTicker(g.config.Frequency)
defer func() {
gossipTicker.Stop()
shutdownWg.Done()
}()

for {
select {
case <-gossipTicker.C:
if err := g.gossip(); err != nil {
log.Warn("failed to gossip", "error", err)
}
case <-shutdownChan:
log.Debug("shutting down gossip")
return
}
}
}

func (g *Gossiper[T, U]) gossip() error {
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
filter := g.set.GetFilter()
bloomBytes, err := filter.Bloom.MarshalBinary()
if err != nil {
return err
}

request := PullGossipRequest{
FilterBytes: bloomBytes,
SaltBytes: filter.Salt,
}
msgBytes, err := g.codec.Marshal(g.codecVersion, request)
if err != nil {
return err
}

for i := 0; i < g.config.PollSize; i++ {
if err := g.client.AppRequestAny(context.TODO(), msgBytes, g.handleResponse); err != nil {
joshua-kim marked this conversation as resolved.
Show resolved Hide resolved
return err
}
}

return nil
}

func (g *Gossiper[T, U]) handleResponse(nodeID ids.NodeID, responseBytes []byte, err error) {
if err != nil {
log.Debug("failed gossip request", "nodeID", nodeID, "error", err)
return
}

response := PullGossipResponse{}
if _, err := g.codec.Unmarshal(responseBytes, &response); err != nil {
log.Debug("failed to unmarshal gossip response", "error", err)
return
}

for _, gossipBytes := range response.GossipBytes {
gossipable := U(new(T))
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
if err := gossipable.Unmarshal(gossipBytes); err != nil {
log.Debug("failed to unmarshal gossip", "error", err, "nodeID", nodeID)
continue
}

log.Debug("received gossip", "nodeID", nodeID, "hash", gossipable.GetHash())
if err := g.set.Add(gossipable); err != nil {
log.Debug("failed to add gossip to the known set", "error", err, "nodeID", nodeID, "id", gossipable.GetHash())
continue
}
}
}