diff --git a/waku/v2/node/localnode.go b/waku/v2/node/localnode.go index 5cb6a8e9..dd9fb7f4 100644 --- a/waku/v2/node/localnode.go +++ b/waku/v2/node/localnode.go @@ -2,14 +2,9 @@ package node import ( "context" - "crypto/ecdsa" - "encoding/binary" "errors" - "math" - "math/rand" "net" "strconv" - "time" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -18,71 +13,25 @@ import ( "go.uber.org/zap" ) -func (w *WakuNode) newLocalnode(priv *ecdsa.PrivateKey) (*enode.LocalNode, error) { - db, err := enode.OpenDB("") - if err != nil { - return nil, err - } - return enode.NewLocalNode(db, priv), nil -} - -func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr) (err error) { - defer func() { - if e := recover(); e != nil { - // Deleting the multiaddr entry, as we could not write it succesfully - localnode.Delete(enr.WithEntry(wenr.MultiaddrENRField, struct{}{})) - err = errors.New("could not write enr record") - } - }() - - var fieldRaw []byte - for _, addr := range addrAggr { - maRaw := addr.Bytes() - maSize := make([]byte, 2) - binary.BigEndian.PutUint16(maSize, uint16(len(maRaw))) - - fieldRaw = append(fieldRaw, maSize...) - fieldRaw = append(fieldRaw, maRaw...) - } - - if len(fieldRaw) != 0 && len(fieldRaw) <= 100 { // Max length for multiaddr field before triggering the 300 bytes limit - localnode.Set(enr.WithEntry(wenr.MultiaddrENRField, fieldRaw)) - } - - // This is to trigger the signing record err due to exceeding 300bytes limit - _ = localnode.Node() - - return nil -} - func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags wenr.WakuEnrBitfield, advertiseAddr []ma.Multiaddr, shouldAutoUpdate bool, log *zap.Logger) error { - localnode.SetFallbackUDP(int(udpPort)) - localnode.Set(enr.WithEntry(wenr.WakuENRField, wakuFlags)) - localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) - - if udpPort > math.MaxUint16 { - return errors.New("invalid udp port number") - } + var options []wenr.ENROption + options = append(options, wenr.WithUDPPort(udpPort)) + options = append(options, wenr.WithWakuBitfield(wakuFlags)) + options = append(options, wenr.WithMultiaddress(multiaddrs...)) if advertiseAddr != nil { // An advertised address disables libp2p address updates // and discv5 predictions - ipAddr, err := selectMostExternalAddress(advertiseAddr) if err != nil { return err } - localnode.SetStaticIP(ipAddr.IP) - localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? - - return writeMultiaddresses(localnode, multiaddrs) + options = append(options, wenr.WithIP(ipAddr)) } else if !shouldAutoUpdate { // We received a libp2p address update. Autoupdate is disabled // Using a static ip will disable endpoint prediction. - localnode.SetStaticIP(ipAddr.IP) - localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? - return writeMultiaddresses(localnode, multiaddrs) + options = append(options, wenr.WithIP(ipAddr)) } else { // We received a libp2p address update, but we should still // allow discv5 to update the enr record. We set the localnode @@ -105,42 +54,9 @@ func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.M localnode.Delete(enr.IPv6{}) localnode.Delete(enr.TCP6(0)) } - - return writeMultiaddresses(localnode, multiaddrs) } -} - -func writeMultiaddresses(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr) error { - // Randomly shuffle multiaddresses - rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(multiaddrs), func(i, j int) { multiaddrs[i], multiaddrs[j] = multiaddrs[j], multiaddrs[i] }) - - // Adding extra multiaddresses. Should probably not exceed the enr max size of 300bytes - var err error - failedOnceWritingENR := false - couldWriteENRatLeastOnce := false - successIdx := -1 - for i := len(multiaddrs) - 1; i >= 0; i-- { - err = writeMultiaddressField(localnode, multiaddrs[0:i]) - if err == nil { - couldWriteENRatLeastOnce = true - successIdx = i - break - } else { - failedOnceWritingENR = true - } - } - - if failedOnceWritingENR && couldWriteENRatLeastOnce { - // Could write a subset of multiaddresses but not all - err = writeMultiaddressField(localnode, multiaddrs[0:successIdx]) - if err != nil { - return errors.New("could not write new ENR") - } - } - - return nil + return wenr.Update(localnode, options...) } func isPrivate(addr *net.TCPAddr) bool { diff --git a/waku/v2/node/wakunode2.go b/waku/v2/node/wakunode2.go index 2b4f493a..5f408563 100644 --- a/waku/v2/node/wakunode2.go +++ b/waku/v2/node/wakunode2.go @@ -184,7 +184,7 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) { w.timesource = timesource.NewDefaultClock() } - w.localNode, err = w.newLocalnode(w.opts.privKey) + w.localNode, err = enr.NewLocalnode(w.opts.privKey) if err != nil { w.log.Error("creating localnode", zap.Error(err)) } diff --git a/waku/v2/protocol/enr/enr_test.go b/waku/v2/protocol/enr/enr_test.go index 5370c614..aa7c4206 100644 --- a/waku/v2/protocol/enr/enr_test.go +++ b/waku/v2/protocol/enr/enr_test.go @@ -1,13 +1,8 @@ package enr import ( - "encoding/binary" - "errors" - "math" - "math/rand" "net" "testing" - "time" gcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" @@ -28,53 +23,26 @@ func TestEnodeToMultiAddr(t *testing.T) { require.Equal(t, expectedMultiAddr, actualMultiAddr.String()) } -func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr) (err error) { - defer func() { - if e := recover(); e != nil { - err = errors.New("could not write enr record") - } - }() - - var fieldRaw []byte - for _, addr := range addrAggr { - maRaw := addr.Bytes() - maSize := make([]byte, 2) - binary.BigEndian.PutUint16(maSize, uint16(len(maRaw))) - - fieldRaw = append(fieldRaw, maSize...) - fieldRaw = append(fieldRaw, maRaw...) - } - - if len(fieldRaw) != 0 { - localnode.Set(enr.WithEntry(MultiaddrENRField, fieldRaw)) - } - - // This is to trigger the signing record err due to exceeding 300bytes limit - _ = localnode.Node() - - return nil -} - // TODO: this function is duplicated in localnode.go. Remove duplication -func updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags WakuEnrBitfield, advertiseAddr *net.IP, shouldAutoUpdate bool, log *zap.Logger) error { - localnode.SetFallbackUDP(int(udpPort)) - localnode.Set(enr.WithEntry(WakuENRField, wakuFlags)) - localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) - if udpPort > math.MaxUint16 { - return errors.New("invalid udp port number") - } +func updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags WakuEnrBitfield, advertiseAddr *net.IP, shouldAutoUpdate bool, log *zap.Logger) error { + var options []ENROption + options = append(options, WithUDPPort(udpPort)) + options = append(options, WithWakuBitfield(wakuFlags)) + options = append(options, WithMultiaddress(multiaddrs...)) if advertiseAddr != nil { // An advertised address disables libp2p address updates // and discv5 predictions - localnode.SetStaticIP(*advertiseAddr) - localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? + nip := &net.TCPAddr{ + IP: *advertiseAddr, + Port: ipAddr.Port, + } + options = append(options, WithIP(nip)) } else if !shouldAutoUpdate { // We received a libp2p address update. Autoupdate is disabled // Using a static ip will disable endpoint prediction. - localnode.SetStaticIP(ipAddr.IP) - localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? + options = append(options, WithIP(ipAddr)) } else { // We received a libp2p address update, but we should still // allow discv5 to update the enr record. We set the localnode @@ -90,7 +58,7 @@ func updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAd localnode.Delete(enr.TCP(0)) } - if ip6 != nil && !ip6.IsUnspecified() { + if ip4 == nil && ip6 != nil && !ip6.IsUnspecified() { localnode.Set(enr.IPv6(ip6)) localnode.Set(enr.TCP6(ipAddr.Port)) } else { @@ -99,40 +67,7 @@ func updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAd } } - // Randomly shuffle multiaddresses - rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(multiaddrs), func(i, j int) { multiaddrs[i], multiaddrs[j] = multiaddrs[j], multiaddrs[i] }) - - // Adding a single extra multiaddress. Should probably not exceed the enr max size of 300bytes - var err error - failedOnceWritingENR := false - couldWriteENRatLeastOnce := false - successIdx := -1 - for i := len(multiaddrs) - 1; i >= 0; i-- { - err = writeMultiaddressField(localnode, multiaddrs[0:i]) - if err == nil { - couldWriteENRatLeastOnce = true - successIdx = i - break - } else { - failedOnceWritingENR = true - } - } - - if failedOnceWritingENR { - if !couldWriteENRatLeastOnce { - // In case multiaddr could not be populated at all - localnode.Delete(enr.WithEntry(MultiaddrENRField, struct{}{})) - } else { - // Could write a subset of multiaddresses but not all - err = writeMultiaddressField(localnode, multiaddrs[0:successIdx]) - if err != nil { - return errors.New("could not write new ENR") - } - } - } - - return nil + return Update(localnode, options...) } func TestMultiaddr(t *testing.T) { diff --git a/waku/v2/protocol/enr/localnode.go b/waku/v2/protocol/enr/localnode.go new file mode 100644 index 00000000..e7a92dfe --- /dev/null +++ b/waku/v2/protocol/enr/localnode.go @@ -0,0 +1,131 @@ +package enr + +import ( + "crypto/ecdsa" + "encoding/binary" + "errors" + "math" + "math/rand" + "net" + "time" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/multiformats/go-multiaddr" +) + +func NewLocalnode(priv *ecdsa.PrivateKey) (*enode.LocalNode, error) { + db, err := enode.OpenDB("") + if err != nil { + return nil, err + } + return enode.NewLocalNode(db, priv), nil +} + +type ENROption func(*enode.LocalNode) error + +func WithMultiaddress(multiaddrs ...multiaddr.Multiaddr) ENROption { + return func(localnode *enode.LocalNode) (err error) { + + // Randomly shuffle multiaddresses + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(multiaddrs), func(i, j int) { multiaddrs[i], multiaddrs[j] = multiaddrs[j], multiaddrs[i] }) + + // Adding extra multiaddresses. Should probably not exceed the enr max size of 300bytes + failedOnceWritingENR := false + couldWriteENRatLeastOnce := false + successIdx := -1 + for i := len(multiaddrs) - 1; i >= 0; i-- { + err = writeMultiaddressField(localnode, multiaddrs[0:i]) + if err == nil { + couldWriteENRatLeastOnce = true + successIdx = i + break + } else { + failedOnceWritingENR = true + } + } + + if failedOnceWritingENR && couldWriteENRatLeastOnce { + // Could write a subset of multiaddresses but not all + err = writeMultiaddressField(localnode, multiaddrs[0:successIdx]) + if err != nil { + return errors.New("could not write new ENR") + } + } + + return nil + } +} + +func WithCapabilities(lightpush, filter, store, relay bool) ENROption { + return func(localnode *enode.LocalNode) (err error) { + wakuflags := NewWakuEnrBitfield(lightpush, filter, store, relay) + return WithWakuBitfield(wakuflags)(localnode) + } +} + +func WithWakuBitfield(flags WakuEnrBitfield) ENROption { + return func(localnode *enode.LocalNode) (err error) { + localnode.Set(enr.WithEntry(WakuENRField, flags)) + return nil + } +} + +func WithIP(ipAddr *net.TCPAddr) ENROption { + return func(localnode *enode.LocalNode) (err error) { + localnode.SetStaticIP(ipAddr.IP) + localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? + return nil + } +} + +func WithUDPPort(udpPort uint) ENROption { + return func(localnode *enode.LocalNode) (err error) { + if udpPort > math.MaxUint16 { + return errors.New("invalid udp port number") + } + localnode.SetFallbackUDP(int(udpPort)) + return nil + } +} + +func Update(localnode *enode.LocalNode, enrOptions ...ENROption) error { + localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) + for _, opt := range enrOptions { + err := opt(localnode) + if err != nil { + return err + } + } + return nil +} + +func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []multiaddr.Multiaddr) (err error) { + defer func() { + if e := recover(); e != nil { + // Deleting the multiaddr entry, as we could not write it succesfully + localnode.Delete(enr.WithEntry(MultiaddrENRField, struct{}{})) + err = errors.New("could not write enr record") + } + }() + + var fieldRaw []byte + for _, addr := range addrAggr { + maRaw := addr.Bytes() + maSize := make([]byte, 2) + binary.BigEndian.PutUint16(maSize, uint16(len(maRaw))) + + fieldRaw = append(fieldRaw, maSize...) + fieldRaw = append(fieldRaw, maRaw...) + } + + if len(fieldRaw) != 0 && len(fieldRaw) <= 100 { // Max length for multiaddr field before triggering the 300 bytes limit + localnode.Set(enr.WithEntry(MultiaddrENRField, fieldRaw)) + } + + // This is to trigger the signing record err due to exceeding 300bytes limit + _ = localnode.Node() + + return nil +} diff --git a/waku/v2/protocol/enr/shards.go b/waku/v2/protocol/enr/shards.go index 09930522..bec90b7a 100644 --- a/waku/v2/protocol/enr/shards.go +++ b/waku/v2/protocol/enr/shards.go @@ -6,25 +6,31 @@ import ( "github.com/waku-org/go-waku/waku/v2/protocol" ) -func SetWakuRelayShardingIndicesList(localnode *enode.LocalNode, rs protocol.RelayShards) error { - value, err := rs.IndicesList() - if err != nil { - return err +func WithWakuRelayShardingIndicesList(rs protocol.RelayShards) ENROption { + return func(localnode *enode.LocalNode) error { + value, err := rs.IndicesList() + if err != nil { + return err + } + localnode.Set(enr.WithEntry(ShardingIndicesListEnrField, value)) + return nil } - localnode.Set(enr.WithEntry(ShardingIndicesListEnrField, value)) - return nil } -func SetWakuRelayShardingBitVector(localnode *enode.LocalNode, rs protocol.RelayShards) error { - localnode.Set(enr.WithEntry(ShardingBitVectorEnrField, rs.BitVector())) - return nil +func WithWakuRelayShardingBitVector(rs protocol.RelayShards) ENROption { + return func(localnode *enode.LocalNode) error { + localnode.Set(enr.WithEntry(ShardingBitVectorEnrField, rs.BitVector())) + return nil + } } -func SetWakuRelaySharding(localnode *enode.LocalNode, rs protocol.RelayShards) error { - if len(rs.Indices) >= 64 { - return SetWakuRelayShardingBitVector(localnode, rs) - } else { - return SetWakuRelayShardingIndicesList(localnode, rs) +func WithtWakuRelaySharding(rs protocol.RelayShards) ENROption { + return func(localnode *enode.LocalNode) error { + if len(rs.Indices) >= 64 { + return WithWakuRelayShardingBitVector(rs)(localnode) + } else { + return WithWakuRelayShardingIndicesList(rs)(localnode) + } } }