go-waku/waku/v2/discv5/discover.go

321 lines
6.3 KiB
Go
Raw Normal View History

2021-11-16 14:22:01 +00:00
package discv5
import (
"context"
"crypto/ecdsa"
"errors"
2021-11-16 14:22:01 +00:00
"net"
"sync"
2022-10-19 19:39:32 +00:00
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-discover/discover"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
2022-12-09 18:09:06 +00:00
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
2021-11-16 14:22:01 +00:00
)
type DiscoveryV5 struct {
2022-10-23 13:13:43 +00:00
sync.RWMutex
2021-11-17 16:19:42 +00:00
params *discV5Parameters
host host.Host
config discover.Config
udpAddr *net.UDPAddr
listener *discover.UDPv5
localnode *enode.LocalNode
peerConnector PeerConnector
NAT nat.Interface
2021-11-16 14:22:01 +00:00
log *zap.Logger
started bool
cancel context.CancelFunc
wg *sync.WaitGroup
2021-11-16 14:22:01 +00:00
}
type discV5Parameters struct {
2021-11-17 16:19:42 +00:00
autoUpdate bool
bootnodes []*enode.Node
udpPort uint
2021-11-17 16:19:42 +00:00
advertiseAddr *net.IP
2021-11-16 14:22:01 +00:00
}
type DiscoveryV5Option func(*discV5Parameters)
var protocolID = [6]byte{'d', '5', 'w', 'a', 'k', 'u'}
2021-11-17 16:19:42 +00:00
func WithAutoUpdate(autoUpdate bool) DiscoveryV5Option {
return func(params *discV5Parameters) {
params.autoUpdate = autoUpdate
}
}
2021-11-16 14:22:01 +00:00
func WithBootnodes(bootnodes []*enode.Node) DiscoveryV5Option {
return func(params *discV5Parameters) {
params.bootnodes = bootnodes
}
}
2021-11-17 16:19:42 +00:00
func WithAdvertiseAddr(addr net.IP) DiscoveryV5Option {
2021-11-16 14:22:01 +00:00
return func(params *discV5Parameters) {
2021-11-17 16:19:42 +00:00
params.advertiseAddr = &addr
2021-11-16 14:22:01 +00:00
}
}
func WithUDPPort(port uint) DiscoveryV5Option {
2021-11-16 14:22:01 +00:00
return func(params *discV5Parameters) {
params.udpPort = port
}
}
func DefaultOptions() []DiscoveryV5Option {
return []DiscoveryV5Option{
WithUDPPort(9000),
}
}
type PeerConnector interface {
PeerChannel() chan<- peer.AddrInfo
}
func NewDiscoveryV5(host host.Host, priv *ecdsa.PrivateKey, localnode *enode.LocalNode, peerConnector PeerConnector, log *zap.Logger, opts ...DiscoveryV5Option) (*DiscoveryV5, error) {
2021-11-16 14:22:01 +00:00
params := new(discV5Parameters)
optList := DefaultOptions()
optList = append(optList, opts...)
for _, opt := range optList {
opt(params)
}
logger := log.Named("discv5")
var NAT nat.Interface = nil
if params.advertiseAddr == nil {
NAT = nat.Any()
}
2021-11-16 14:22:01 +00:00
return &DiscoveryV5{
host: host,
peerConnector: peerConnector,
params: params,
NAT: NAT,
wg: &sync.WaitGroup{},
localnode: localnode,
2021-11-16 14:22:01 +00:00
config: discover.Config{
PrivateKey: priv,
Bootnodes: params.bootnodes,
V5Config: discover.V5Config{
ProtocolID: &protocolID,
},
2021-11-16 14:22:01 +00:00
},
udpAddr: &net.UDPAddr{
2021-11-17 16:19:42 +00:00
IP: net.IPv4zero,
Port: int(params.udpPort),
2021-11-16 14:22:01 +00:00
},
2022-06-13 18:30:35 +00:00
log: logger,
2021-11-16 14:22:01 +00:00
}, nil
}
2022-10-23 13:13:43 +00:00
func (d *DiscoveryV5) Node() *enode.Node {
return d.localnode.Node()
}
func (d *DiscoveryV5) listen(ctx context.Context) error {
2021-11-16 14:22:01 +00:00
conn, err := net.ListenUDP("udp", d.udpAddr)
if err != nil {
return err
}
d.udpAddr = conn.LocalAddr().(*net.UDPAddr)
if d.NAT != nil && !d.udpAddr.IP.IsLoopback() {
d.wg.Add(1)
go func() {
defer d.wg.Done()
nat.Map(d.NAT, ctx.Done(), "udp", d.udpAddr.Port, d.udpAddr.Port, "go-waku discv5 discovery")
}()
}
d.localnode.SetFallbackUDP(d.udpAddr.Port)
2021-11-16 14:22:01 +00:00
listener, err := discover.ListenV5(conn, d.localnode, d.config)
if err != nil {
return err
}
d.listener = listener
d.log.Info("started Discovery V5",
zap.Stringer("listening", d.udpAddr),
logging.TCPAddr("advertising", d.localnode.Node().IP(), d.localnode.Node().TCP()))
d.log.Info("Discovery V5: discoverable ENR ", logging.ENode("enr", d.localnode.Node()))
2021-11-17 16:19:42 +00:00
return nil
}
func (d *DiscoveryV5) Start(ctx context.Context) error {
2021-11-17 16:19:42 +00:00
d.Lock()
defer d.Unlock()
d.wg.Wait() // Waiting for any go routines to stop
ctx, cancel := context.WithCancel(ctx)
d.cancel = cancel
2022-10-23 13:13:43 +00:00
d.started = true
err := d.listen(ctx)
2021-11-17 16:19:42 +00:00
if err != nil {
return err
}
d.wg.Add(1)
go d.runDiscoveryV5Loop(ctx)
2021-11-16 14:22:01 +00:00
return nil
}
2022-12-09 17:50:20 +00:00
func (d *DiscoveryV5) SetBootnodes(nodes []*enode.Node) error {
2022-12-09 18:09:06 +00:00
return d.listener.SetFallbackNodes(nodes)
2022-12-09 17:50:20 +00:00
}
2021-11-16 14:22:01 +00:00
func (d *DiscoveryV5) Stop() {
2021-11-17 16:19:42 +00:00
d.Lock()
defer d.Unlock()
2023-01-06 22:37:57 +00:00
if d.cancel == nil {
return
}
2023-01-06 22:37:57 +00:00
d.cancel()
2022-10-23 13:13:43 +00:00
d.started = false
2023-01-04 18:46:22 +00:00
if d.listener != nil {
d.listener.Close()
d.listener = nil
d.log.Info("stopped Discovery V5")
}
d.wg.Wait()
2021-11-17 16:19:42 +00:00
}
2022-03-18 19:50:10 +00:00
/*
2021-11-16 14:22:01 +00:00
func isWakuNode(node *enode.Node) bool {
enrField := new(utils.WakuEnrBitfield)
if err := node.Record().Load(enr.WithEntry(utils.WakuENRField, &enrField)); err != nil {
2021-11-16 14:22:01 +00:00
if !enr.IsNotFound(err) {
utils.Logger().Named("discv5").Error("could not retrieve port for enr ", zap.Any("node", node))
2021-11-16 14:22:01 +00:00
}
return false
}
if enrField != nil {
return *enrField != uint8(0)
}
return false
}
2022-03-18 19:50:10 +00:00
*/
2021-11-16 14:22:01 +00:00
func evaluateNode(node *enode.Node) bool {
if node == nil {
2021-11-16 14:22:01 +00:00
return false
}
2022-03-10 22:14:50 +00:00
// TODO: consider node filtering based on ENR; we do not filter based on ENR in the first waku discv5 beta stage
/*if !isWakuNode(node) {
2021-11-16 14:22:01 +00:00
return false
}*/
2021-11-16 14:22:01 +00:00
_, err := utils.EnodeToPeerInfo(node)
2021-11-17 16:19:42 +00:00
2021-11-16 14:22:01 +00:00
if err != nil {
utils.Logger().Named("discv5").Error("obtaining peer info from enode", logging.ENode("enr", node), zap.Error(err))
2021-11-16 14:22:01 +00:00
return false
}
return true
}
func (d *DiscoveryV5) Iterator() (enode.Iterator, error) {
if d.listener == nil {
return nil, errors.New("no discv5 listener")
2021-11-16 14:22:01 +00:00
}
iterator := d.listener.RandomNodes()
return enode.Filter(iterator, evaluateNode), nil
2021-11-16 14:22:01 +00:00
}
func (d *DiscoveryV5) iterate(ctx context.Context) {
iterator, err := d.Iterator()
if err != nil {
d.log.Debug("obtaining iterator", zap.Error(err))
return
}
defer iterator.Close()
2021-11-16 14:22:01 +00:00
for {
if ctx.Err() != nil {
2021-11-17 16:19:42 +00:00
break
}
2021-11-16 14:22:01 +00:00
exists := iterator.Next()
if !exists {
break
}
addresses, err := utils.Multiaddress(iterator.Node())
2021-11-16 14:22:01 +00:00
if err != nil {
d.log.Error("extracting multiaddrs from enr", zap.Error(err))
2021-11-16 14:22:01 +00:00
continue
}
peerAddrs, err := peer.AddrInfosFromP2pAddrs(addresses...)
2021-11-16 14:22:01 +00:00
if err != nil {
d.log.Error("converting multiaddrs to addrinfos", zap.Error(err))
2021-11-16 14:22:01 +00:00
continue
}
if len(peerAddrs) != 0 {
select {
case <-ctx.Done():
return
case d.peerConnector.PeerChannel() <- peerAddrs[0]:
}
2021-11-16 14:22:01 +00:00
}
}
}
func (d *DiscoveryV5) runDiscoveryV5Loop(ctx context.Context) {
defer d.wg.Done()
2021-11-16 14:22:01 +00:00
ch := make(chan struct{}, 1)
ch <- struct{}{} // Initial execution
2021-11-16 14:22:01 +00:00
restartLoop:
for {
select {
case <-ch:
if d.listener == nil {
break
}
d.iterate(ctx)
ch <- struct{}{}
case <-ctx.Done():
close(ch)
break restartLoop
2021-11-16 14:22:01 +00:00
}
2022-10-23 13:13:43 +00:00
}
d.log.Warn("Discv5 loop stopped")
2021-11-16 14:22:01 +00:00
}
2022-10-23 13:13:43 +00:00
func (d *DiscoveryV5) IsStarted() bool {
d.RLock()
defer d.RUnlock()
return d.started
}