Limit number of proxied records and proxy only recently observed nodes (#1227)
* Limit number of proxied records and proxy only recently observed nodes * Explicitly verify that limit is preserved and records removed eventually
This commit is contained in:
parent
9f8f0089a3
commit
fbd3c9ca80
|
@ -5,8 +5,10 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
@ -22,7 +24,9 @@ var (
|
|||
bootnodes = StringSlice{}
|
||||
topics = StringSlice{}
|
||||
les = IntSlice{}
|
||||
useEthereum = flag.Bool("use-ethereum-boot", false, "If true ethereum bootnodes will be used")
|
||||
useEthereum = flag.Bool("use-ethereum-boot", false, "If true ethereum bootnodes will be used.")
|
||||
limit = flag.Int("limit", 100, "Limit the number of proxied nodes.")
|
||||
livenessWindow = flag.Duration("liveness-window", 10*time.Minute, "Stop proxying record if it wasn't found again during specified window.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -65,7 +69,12 @@ func main() {
|
|||
t := t
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := discovery.ProxyToRendezvous(v5, rendezvousServers, t, stop); err != nil {
|
||||
if err := discovery.ProxyToRendezvous(v5, stop, &event.Feed{}, discovery.ProxyOptions{
|
||||
Topic: t,
|
||||
Servers: rendezvousServers,
|
||||
Limit: *limit,
|
||||
LivenessWindow: *livenessWindow,
|
||||
}); err != nil {
|
||||
log.Error("proxying to rendezvous servers failed", "servers", rendezvousNodes, "topic", t, "error", err)
|
||||
}
|
||||
wg.Done()
|
||||
|
|
|
@ -6,26 +6,50 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
const (
|
||||
proxyStart = "start"
|
||||
proxyStop = "stop"
|
||||
)
|
||||
|
||||
type proxyEvent struct {
|
||||
ID discv5.NodeID
|
||||
Type string
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type ProxyOptions struct {
|
||||
Topic string
|
||||
Servers []ma.Multiaddr
|
||||
Limit int
|
||||
LivenessWindow time.Duration
|
||||
}
|
||||
|
||||
// ProxyToRendezvous proxies records discovered using original to rendezvous servers for specified topic.
|
||||
func ProxyToRendezvous(original Discovery, servers []ma.Multiaddr, topic string, stop chan struct{}) error {
|
||||
func ProxyToRendezvous(original Discovery, stop chan struct{}, feed *event.Feed, opts ProxyOptions) error {
|
||||
var (
|
||||
identities = map[discv5.NodeID]*Rendezvous{}
|
||||
lastSeen = map[discv5.NodeID]time.Time{}
|
||||
closers = map[discv5.NodeID]chan struct{}{}
|
||||
period = make(chan time.Duration, 1)
|
||||
found = make(chan *discv5.Node, 10)
|
||||
lookup = make(chan bool)
|
||||
total = 0
|
||||
livenessWatcher = time.NewTicker(opts.LivenessWindow / 10)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer livenessWatcher.Stop()
|
||||
period <- 1 * time.Second
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := original.Discover(topic, period, found, lookup); err != nil {
|
||||
log.Error("discover request failed", "topic", topic, "error", err)
|
||||
if err := original.Discover(opts.Topic, period, found, lookup); err != nil {
|
||||
log.Error("discover request failed", "topic", opts.Topic, "error", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
@ -36,24 +60,52 @@ func ProxyToRendezvous(original Discovery, servers []ma.Multiaddr, topic string,
|
|||
wg.Wait()
|
||||
return nil
|
||||
case <-lookup:
|
||||
case n := <-found:
|
||||
if _, exist := identities[n.ID]; exist {
|
||||
case <-livenessWatcher.C:
|
||||
for n := range identities {
|
||||
if _, exist := lastSeen[n]; !exist {
|
||||
continue
|
||||
}
|
||||
log.Info("proxying new record", "topic", topic, "identity", n.String())
|
||||
// closeRequest is sent every time window after record was seen.
|
||||
// record must be discovered again during same time window otherwise it will be removed.
|
||||
if time.Since(lastSeen[n]) >= opts.LivenessWindow {
|
||||
close(closers[n])
|
||||
_ = identities[n].Stop()
|
||||
delete(identities, n)
|
||||
delete(lastSeen, n)
|
||||
delete(closers, n)
|
||||
total--
|
||||
log.Info("proxy for a record was removed", "identity", n.String(), "total", total)
|
||||
feed.Send(proxyEvent{n, proxyStop, time.Now()})
|
||||
}
|
||||
}
|
||||
case n := <-found:
|
||||
_, exist := identities[n.ID]
|
||||
// skip new record if we reached a limit.
|
||||
if !exist && total == opts.Limit {
|
||||
continue
|
||||
}
|
||||
lastSeen[n.ID] = time.Now()
|
||||
if exist {
|
||||
log.Debug("received an update for existing identity", "identity", n.String())
|
||||
continue
|
||||
}
|
||||
feed.Send(proxyEvent{n.ID, proxyStart, lastSeen[n.ID]})
|
||||
total++
|
||||
log.Info("proxying new record", "topic", opts.Topic, "identity", n.String(), "total", total)
|
||||
record, err := makeProxiedENR(n)
|
||||
if err != nil {
|
||||
log.Error("error converting discovered node to ENR", "node", n.String(), "error", err)
|
||||
}
|
||||
r := NewRendezvousWithENR(servers, record)
|
||||
r := NewRendezvousWithENR(opts.Servers, record)
|
||||
identities[n.ID] = r
|
||||
closers[n.ID] = make(chan struct{})
|
||||
if err := r.Start(); err != nil {
|
||||
log.Error("unable to start rendezvous proxying", "servers", servers, "error", err)
|
||||
log.Error("unable to start rendezvous proxying", "servers", opts.Servers, "error", err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if err := r.Register(topic, stop); err != nil {
|
||||
log.Error("register error", "topic", topic, "error", err)
|
||||
if err := r.Register(opts.Topic, closers[n.ID]); err != nil {
|
||||
log.Error("register error", "topic", opts.Topic, "error", err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
|
|
@ -2,10 +2,12 @@ package discovery
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/status-im/rendezvous"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -15,42 +17,87 @@ func TestProxyToRendezvous(t *testing.T) {
|
|||
var (
|
||||
topic = "test"
|
||||
id = 101
|
||||
limited = 102
|
||||
limit = 1
|
||||
reg = newRegistry()
|
||||
original = &fake{id: 110, registry: reg, started: true}
|
||||
srv = makeTestRendezvousServer(t, "/ip4/127.0.0.1/tcp/7788")
|
||||
stop = make(chan struct{})
|
||||
feed = &event.Feed{}
|
||||
liveness = 100 * time.Millisecond
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
client, err := rendezvous.NewEphemeral()
|
||||
require.NoError(t, err)
|
||||
reg.Add(topic, id)
|
||||
reg.Add(topic, limited)
|
||||
wg.Add(1)
|
||||
events := make(chan proxyEvent, 10)
|
||||
sub := feed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
require.NoError(t, ProxyToRendezvous(original, []ma.Multiaddr{srv.Addr()}, topic, stop))
|
||||
require.NoError(t, ProxyToRendezvous(original, stop, feed, ProxyOptions{
|
||||
Topic: topic,
|
||||
Servers: []ma.Multiaddr{srv.Addr()},
|
||||
Limit: limit,
|
||||
LivenessWindow: liveness,
|
||||
}))
|
||||
}()
|
||||
timer := time.After(3 * time.Second)
|
||||
ticker := time.Tick(100 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-timer:
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
require.FailNow(t, "failed waiting for record to be proxied")
|
||||
case <-ticker:
|
||||
require.NoError(t, Consistently(func() (bool, error) {
|
||||
records, err := client.Discover(context.TODO(), srv.Addr(), topic, 10)
|
||||
if err != nil {
|
||||
continue
|
||||
if err != nil && len(records) < limit {
|
||||
return true, nil
|
||||
}
|
||||
if len(records) != 1 {
|
||||
continue
|
||||
if len(records) > limit {
|
||||
return false, fmt.Errorf("more records than expected: %d != %d", len(records), limit)
|
||||
}
|
||||
var proxied Proxied
|
||||
require.NoError(t, records[0].Load(&proxied))
|
||||
require.Equal(t, proxied[0], byte(id))
|
||||
if err := records[0].Load(&proxied); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if proxied[0] != byte(id) {
|
||||
return false, fmt.Errorf("returned %v instead of %v", proxied[0], id)
|
||||
}
|
||||
return true, nil
|
||||
}, time.Second, 100*time.Millisecond))
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
eventSlice := []proxyEvent{}
|
||||
func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-events:
|
||||
eventSlice = append(eventSlice, e)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
require.Len(t, eventSlice, 2)
|
||||
require.Equal(t, byte(id), eventSlice[0].ID[0])
|
||||
require.Equal(t, proxyStart, eventSlice[0].Type)
|
||||
require.Equal(t, byte(id), eventSlice[1].ID[0])
|
||||
require.Equal(t, proxyStop, eventSlice[1].Type)
|
||||
require.True(t, eventSlice[1].Time.Sub(eventSlice[0].Time) > liveness)
|
||||
}
|
||||
|
||||
func Consistently(f func() (bool, error), timeout, period time.Duration) (err error) {
|
||||
timer := time.After(timeout)
|
||||
ticker := time.Tick(period)
|
||||
var cont bool
|
||||
for {
|
||||
select {
|
||||
case <-timer:
|
||||
return err
|
||||
case <-ticker:
|
||||
cont, err = f()
|
||||
if cont {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue