From db08fa33ac192c1eddf8bc239ee14b5fe295225f Mon Sep 17 00:00:00 2001 From: corverroos Date: Mon, 18 Apr 2022 14:45:27 +0200 Subject: [PATCH] p2p: refactor and cleanup bootnode relay wiring (#428) - Decouple relays and bootnodes from udpnode. - Improve gater tests category: refactor ticket: #413 --- app/app.go | 32 ++++++---- cmd/bootnode.go | 4 +- p2p/bootnode.go | 116 +++++++++++++++++++++++++++++++++++++ p2p/discovery.go | 113 +++--------------------------------- p2p/discovery_test.go | 4 +- p2p/gater.go | 4 +- p2p/gater_internal_test.go | 100 -------------------------------- p2p/gater_test.go | 80 +++++++++++++++++++++++++ p2p/p2p.go | 26 ++++----- p2p/peer.go | 21 ------- p2p/relay.go | 23 ++++++++ 11 files changed, 262 insertions(+), 261 deletions(-) create mode 100644 p2p/bootnode.go delete mode 100644 p2p/gater_internal_test.go create mode 100644 p2p/gater_test.go diff --git a/app/app.go b/app/app.go index bd9ccdee1..47cdb5d4a 100644 --- a/app/app.go +++ b/app/app.go @@ -169,34 +169,38 @@ func wireP2P(ctx context.Context, life *lifecycle.Manager, conf Config, manifest var err error p2pKey, err = p2p.LoadPrivKey(conf.DataDir) if err != nil { - return nil, nil, errors.Wrap(err, "load p2p key") + return nil, nil, err } } localEnode, peerDB, err := p2p.NewLocalEnode(conf.P2P, p2pKey) if err != nil { - return nil, nil, errors.Wrap(err, "create local enode") + return nil, nil, err } - udpNode, err := p2p.NewUDPNode(ctx, conf.P2P, localEnode, p2pKey, manifest.Peers) + bootnodes, err := p2p.NewUDPBootnodes(ctx, conf.P2P, manifest.Peers, localEnode.ID()) if err != nil { - return nil, nil, errors.Wrap(err, "start discv5 listener") + return nil, nil, err } - connGater, err := p2p.NewConnGater(manifest.PeerIDs(), udpNode.Relays) + udpNode, err := p2p.NewUDPNode(conf.P2P, localEnode, p2pKey, bootnodes) if err != nil { - return nil, nil, errors.Wrap(err, "connection gater") + return nil, nil, err } - tcpNode, err := p2p.NewTCPNode(conf.P2P, p2pKey, connGater, udpNode, manifest.Peers, p2p.EmptyAdvertisedAddrs) + relays, err := p2p.NewRelays(conf.P2P, bootnodes) if err != nil { - return nil, nil, errors.Wrap(err, "new p2p node", z.Str("allowlist", conf.P2P.Allowlist)) + return nil, nil, err } - if conf.P2P.BootnodeRelay { - for _, relay := range udpNode.Relays { - life.RegisterStart(lifecycle.AsyncAppCtx, lifecycle.StartRelay, p2p.NewRelayReserver(tcpNode, relay)) - } + connGater, err := p2p.NewConnGater(manifest.PeerIDs(), relays) + if err != nil { + return nil, nil, err + } + + tcpNode, err := p2p.NewTCPNode(conf.P2P, p2pKey, connGater, udpNode, manifest.Peers, relays) + if err != nil { + return nil, nil, err } if !conf.TestConfig.DisablePing { @@ -209,6 +213,10 @@ func wireP2P(ctx context.Context, life *lifecycle.Manager, conf Config, manifest life.RegisterStop(lifecycle.StopP2PTCPNode, lifecycle.HookFuncErr(tcpNode.Close)) life.RegisterStop(lifecycle.StopP2PUDPNode, lifecycle.HookFuncMin(udpNode.Close)) + for _, relay := range relays { + life.RegisterStart(lifecycle.AsyncAppCtx, lifecycle.StartRelay, p2p.NewRelayReserver(tcpNode, relay)) + } + return tcpNode, localEnode, nil } diff --git a/cmd/bootnode.go b/cmd/bootnode.go index 98ca6b94e..f92c4e79f 100644 --- a/cmd/bootnode.go +++ b/cmd/bootnode.go @@ -103,7 +103,7 @@ func RunBootnode(ctx context.Context, config BootnodeConfig) error { } defer db.Close() - udpNode, err := p2p.NewUDPNode(ctx, config.P2PConfig, localEnode, key, nil) + udpNode, err := p2p.NewUDPNode(config.P2PConfig, localEnode, key, nil) if err != nil { return errors.Wrap(err, "") } @@ -113,7 +113,7 @@ func RunBootnode(ctx context.Context, config BootnodeConfig) error { p2pErr := make(chan error, 1) go func() { if config.P2PRelay { - tcpNode, err := p2p.NewTCPNode(config.P2PConfig, key, p2p.NewOpenGater(), udpNode, nil, p2p.EmptyAdvertisedAddrs) + tcpNode, err := p2p.NewTCPNode(config.P2PConfig, key, p2p.NewOpenGater(), udpNode, nil, nil) if err != nil { p2pErr <- err return diff --git a/p2p/bootnode.go b/p2p/bootnode.go new file mode 100644 index 000000000..7b7a59609 --- /dev/null +++ b/p2p/bootnode.go @@ -0,0 +1,116 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +package p2p + +import ( + "context" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/app/log" + "github.com/obolnetwork/charon/app/z" +) + +// NewUDPBootnodes returns the udp bootnodes from the config. +func NewUDPBootnodes(ctx context.Context, config Config, peers []Peer, + localEnode enode.ID, +) ([]*enode.Node, error) { + var resp []*enode.Node + for _, rawURL := range config.UDPBootnodes { + if strings.HasPrefix(rawURL, "http") { + // Resolve bootnode ENR via http, retry for 1min with 5sec backoff. + inner, cancel := context.WithTimeout(ctx, time.Minute) + var err error + rawURL, err = queryBootnodeENR(inner, rawURL, time.Second*5) + cancel() + if err != nil { + return nil, err + } + } + + node, err := enode.Parse(enode.V4ID{}, rawURL) + if err != nil { + return nil, errors.Wrap(err, "invalid bootnode address") + } + + resp = append(resp, node) + } + + if config.UDPBootManifest { + for _, p := range peers { + if p.Enode.ID() == localEnode { + // Do not include ourselves as bootnode. + continue + } + node := p.Enode // Copy loop variable + resp = append(resp, &node) + } + } + + return resp, nil +} + +// queryBootnodeENR returns the bootnode ENR via a http GET query to the url. +// +// This supports resolving bootnode ENR from known http URLs which is handy +// when bootnodes are deployed in docker-compose or kubernetes +// +// It retries until the context is cancelled. +func queryBootnodeENR(ctx context.Context, bootnodeURL string, backoff time.Duration) (string, error) { + parsedURL, err := url.Parse(bootnodeURL) + if err != nil { + return "", errors.Wrap(err, "parse bootnode url") + } else if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return "", errors.New("invalid bootnode url") + } + + var client http.Client + for ctx.Err() == nil { + req, err := http.NewRequestWithContext(ctx, "GET", bootnodeURL, nil) + if err != nil { + return "", errors.Wrap(err, "new request") + } + + resp, err := client.Do(req) + if err != nil { + log.Warn(ctx, "Failure querying bootnode ENR, trying again in 5s...", z.Err(err)) + time.Sleep(backoff) + + continue + } else if resp.StatusCode/100 != 2 { + return "", errors.New("non-200 response querying bootnode ENR", + z.Int("status_code", resp.StatusCode)) + } + + b, err := io.ReadAll(resp.Body) + _ = resp.Body.Close() + if err != nil { + return "", errors.Wrap(err, "read response body") + } + + log.Info(ctx, "Queried bootnode ENR", z.Str("url", bootnodeURL), z.Str("enr", string(b))) + + return string(b), nil + } + + return "", errors.Wrap(ctx.Err(), "timeout querying bootnode ENR") +} diff --git a/p2p/discovery.go b/p2p/discovery.go index 405dd2f36..bfb1fb322 100644 --- a/p2p/discovery.go +++ b/p2p/discovery.go @@ -16,14 +16,8 @@ package p2p import ( - "context" "crypto/ecdsa" - "io" "net" - "net/http" - "net/url" - "strings" - "time" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" @@ -31,8 +25,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/obolnetwork/charon/app/errors" - "github.com/obolnetwork/charon/app/log" - "github.com/obolnetwork/charon/app/z" ) // UDPNode wraps a discv5 udp node and adds the bootnodes relays. @@ -42,63 +34,23 @@ type UDPNode struct { } // NewUDPNode starts and returns a discv5 UDP implementation. -func NewUDPNode(ctx context.Context, config Config, ln *enode.LocalNode, key *ecdsa.PrivateKey, - peers []Peer, -) (UDPNode, error) { - // Setup bootnodes and relays - var ( - bootnodes []*enode.Node - bootRelays []Peer - err error - ) - for _, bootnode := range config.UDPBootnodes { - if strings.HasPrefix(bootnode, "http") { - // Query bootnode ENR via http, retry for 1min with 5sec backoff. - inner, cancel := context.WithTimeout(ctx, time.Minute) - bootnode, err = queryBootnodeENR(inner, bootnode, time.Second*5) - cancel() - if err != nil { - return UDPNode{}, err - } - } - - peer, err := newRelayPeer(bootnode) - if err != nil { - return UDPNode{}, err - } - - bootnodes = append(bootnodes, &peer.Enode) - - if config.BootnodeRelay { - bootRelays = append(bootRelays, peer) - } - } - - if config.UDPBootManifest { - for _, p := range peers { - if ln.ID() == p.Enode.ID() { - // Do not add local node as bootnode - continue - } - node := p.Enode // Copy loop variable - bootnodes = append(bootnodes, &node) - } - } - +func NewUDPNode(config Config, ln *enode.LocalNode, + key *ecdsa.PrivateKey, bootnodes []*enode.Node, +) (*discover.UDPv5, error) { // Setup discv5 udp listener. udpAddr, err := net.ResolveUDPAddr("udp", config.UDPAddr) if err != nil { - return UDPNode{}, errors.Wrap(err, "resolve udp address") + return nil, errors.Wrap(err, "resolve udp address") } conn, err := net.ListenUDP("udp", udpAddr) if err != nil { - return UDPNode{}, errors.Wrap(err, "parse udp address") + return nil, errors.Wrap(err, "parse udp address") } netlist, err := netutil.ParseNetlist(config.Allowlist) if err != nil { - return UDPNode{}, errors.Wrap(err, "parse allow list") + return nil, errors.Wrap(err, "parse allow list") } node, err := discover.ListenV5(conn, ln, discover.Config{ @@ -107,59 +59,10 @@ func NewUDPNode(ctx context.Context, config Config, ln *enode.LocalNode, key *ec Bootnodes: bootnodes, }) if err != nil { - return UDPNode{}, errors.Wrap(err, "discv5 listen") - } - - return UDPNode{ - UDPv5: node, - Relays: bootRelays, - }, nil -} - -// queryBootnodeENR returns the bootnode ENR via a http GET query to the url. -// -// This supports resolving bootnode ENR from known http URLs which is handy -// when bootnodes are deployed in docker-compose or kubernetes -// -// It retries until the context is cancelled. -func queryBootnodeENR(ctx context.Context, bootnodeURL string, backoff time.Duration) (string, error) { - parsedURL, err := url.Parse(bootnodeURL) - if err != nil { - return "", errors.Wrap(err, "parse bootnode url") - } else if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { - return "", errors.New("invalid bootnode url") - } - - var client http.Client - for ctx.Err() == nil { - req, err := http.NewRequestWithContext(ctx, "GET", bootnodeURL, nil) - if err != nil { - return "", errors.Wrap(err, "new request") - } - - resp, err := client.Do(req) - if err != nil { - log.Warn(ctx, "Failure querying bootnode ENR, trying again in 5s...", z.Err(err)) - time.Sleep(backoff) - - continue - } else if resp.StatusCode/100 != 2 { - return "", errors.New("non-200 response querying bootnode ENR", - z.Int("status_code", resp.StatusCode)) - } - - b, err := io.ReadAll(resp.Body) - _ = resp.Body.Close() - if err != nil { - return "", errors.Wrap(err, "read response body") - } - - log.Info(ctx, "Queried bootnode ENR", z.Str("url", bootnodeURL), z.Str("enr", string(b))) - - return string(b), nil + return nil, errors.Wrap(err, "discv5 listen") } - return "", errors.Wrap(ctx.Err(), "timeout querying bootnode ENR") + return node, nil } // NewLocalEnode returns a local enode and a peer DB or an error. diff --git a/p2p/discovery_test.go b/p2p/discovery_test.go index 6b461221a..782e12ded 100644 --- a/p2p/discovery_test.go +++ b/p2p/discovery_test.go @@ -16,7 +16,6 @@ package p2p_test import ( - "context" "crypto/ecdsa" "fmt" "math/rand" @@ -30,7 +29,6 @@ import ( ) func TestExternalHost(t *testing.T) { - ctx := context.Background() p2pKey, err := ecdsa.GenerateKey(crypto.S256(), rand.New(rand.NewSource(0))) require.NoError(t, err) @@ -47,7 +45,7 @@ func TestExternalHost(t *testing.T) { require.NoError(t, err) defer db.Close() - udpNode, err := p2p.NewUDPNode(ctx, config, localNode, p2pKey, nil) + udpNode, err := p2p.NewUDPNode(config, localNode, p2pKey, nil) testutil.SkipIfBindErr(t, err) require.NoError(t, err) defer udpNode.Close() diff --git a/p2p/gater.go b/p2p/gater.go index 7178e2bec..30f6aafca 100644 --- a/p2p/gater.go +++ b/p2p/gater.go @@ -33,8 +33,8 @@ func NewConnGater(peers []peer.ID, relays []Peer) (ConnGater, error) { } // Allow connections to/from relays. - for _, bootnode := range relays { - peerMap[bootnode.ID] = true + for _, relay := range relays { + peerMap[relay.ID] = true } return ConnGater{ diff --git a/p2p/gater_internal_test.go b/p2p/gater_internal_test.go deleted file mode 100644 index e18edc2b2..000000000 --- a/p2p/gater_internal_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2022 Obol Labs Inc. -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . - -package p2p - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "fmt" - "testing" - - gcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/stretchr/testify/require" -) - -func TestInterceptSecured(t *testing.T) { - c := ConnGater{ - peerIDs: map[peer.ID]bool{}, - } - tests := map[string]struct { - peerID peer.ID - expected bool - setPeerToKnown bool - logMsg string - }{ - "unknown peer": {"unknown_peer_id", false, false, "should reject connection attempt from unknown peers"}, - "known peer": {"known_peer_id", true, true, "should accept connection attempt from known peers"}, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - if tc.setPeerToKnown { - c.peerIDs[tc.peerID] = true - } - allow := c.InterceptSecured(0, tc.peerID, nil) - require.Equal(t, tc.expected, allow, tc.logMsg) - }) - } -} - -// Tests if node A rejects connection attempt from unknown node B. -func TestP2PConnGating(t *testing.T) { - c := ConnGater{ - peerIDs: map[peer.ID]bool{}, - } - - // create node A - p2pConfigA := Config{TCPAddrs: []string{"127.0.0.1:3030"}} - prvKeyA, _, err := crypto.GenerateSecp256k1Key(rand.Reader) - if err != nil { - t.Fatal("private key generation for A failed", err) - } - nodeA, err := NewTCPNode(p2pConfigA, convertPrivKey(prvKeyA), c, UDPNode{}, nil, DefaultAdvertisedAddrs) - if err != nil { - t.Fatal("couldn't instantiate new node A", err) - } - - // create node B - p2pConfigB := Config{TCPAddrs: []string{"127.0.0.1:3031"}} - prvKeyB, _, err := crypto.GenerateSecp256k1Key(rand.Reader) - if err != nil { - t.Fatal("private key generation for B failed", err) - } - nodeB, err := NewTCPNode(p2pConfigB, convertPrivKey(prvKeyB), c, UDPNode{}, nil, DefaultAdvertisedAddrs) - if err != nil { - t.Fatal("couldn't instantiate new node B", err) - } - - // Let B attempt connection to A - err = nodeB.Connect(context.Background(), peer.AddrInfo{ID: nodeA.ID(), Addrs: nodeA.Addrs()}) - require.Error(t, err) - require.Contains(t, err.Error(), fmt.Sprintf("gater rejected connection with peer %s and addr %s", nodeA.ID(), nodeA.Addrs()[0])) -} - -func TestOpenGater(t *testing.T) { - gater := NewOpenGater() - require.True(t, gater.InterceptSecured(0, "", nil)) -} - -func convertPrivKey(privkey crypto.PrivKey) *ecdsa.PrivateKey { - typeAssertedKey := (*ecdsa.PrivateKey)(privkey.(*crypto.Secp256k1PrivateKey)) - typeAssertedKey.Curve = gcrypto.S256() // Temporary hack, so libp2p Secp256k1 is recognized as geth Secp256k1 in disc v5.1. - - return typeAssertedKey -} diff --git a/p2p/gater_test.go b/p2p/gater_test.go new file mode 100644 index 000000000..b3b412156 --- /dev/null +++ b/p2p/gater_test.go @@ -0,0 +1,80 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +package p2p_test + +import ( + "context" + "crypto/rand" + "fmt" + "testing" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" + + "github.com/obolnetwork/charon/p2p" +) + +func TestInterceptSecured(t *testing.T) { + tests := []struct { + config peer.ID + query peer.ID + allow bool + }{ + {"peer", "unknown", false}, + {"peer", "peer", true}, + } + + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + c, err := p2p.NewConnGater([]peer.ID{test.config}, nil) + require.NoError(t, err) + + allow := c.InterceptSecured(0, test.query, nil) + require.Equal(t, test.allow, allow) + }) + } +} + +func TestP2PConnGating(t *testing.T) { + c, err := p2p.NewConnGater(nil, nil) + require.NoError(t, err) + + keyA, _, err := crypto.GenerateSecp256k1Key(rand.Reader) + require.NoError(t, err) + nodeA, err := libp2p.New(libp2p.Identity(keyA), libp2p.ConnectionGater(c)) + require.NoError(t, err) + + keyB, _, err := crypto.GenerateSecp256k1Key(rand.Reader) + require.NoError(t, err) + nodeB, err := libp2p.New(libp2p.Identity(keyB)) + require.NoError(t, err) + + addr := peer.AddrInfo{ + ID: nodeB.ID(), + Addrs: nodeB.Addrs(), + } + + err = nodeA.Connect(context.Background(), addr) + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf("gater rejected connection with peer %s and addr %s", addr.ID, addr.Addrs[0])) +} + +func TestOpenGater(t *testing.T) { + gater := p2p.NewOpenGater() + require.True(t, gater.InterceptSecured(0, "", nil)) +} diff --git a/p2p/p2p.go b/p2p/p2p.go index 45cffe65d..582e798ce 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -21,6 +21,7 @@ import ( "fmt" "sync" + "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p-core/crypto" @@ -28,7 +29,6 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/routing" noise "github.com/libp2p/go-libp2p-noise" - p2pcfg "github.com/libp2p/go-libp2p/config" ma "github.com/multiformats/go-multiaddr" "github.com/obolnetwork/charon/app/errors" @@ -37,17 +37,9 @@ import ( "github.com/obolnetwork/charon/app/z" ) -// EmptyAdvertisedAddrs defines a p2pcfg.AddrsFactory that does not advertise -// addresses via libp2p, since we use discv5 for peer discovery. -var EmptyAdvertisedAddrs = func([]ma.Multiaddr) []ma.Multiaddr { return nil } - -// DefaultAdvertisedAddrs defines the default p2pcfg.AddrsFactory that advertises -// the bind addresses. -var DefaultAdvertisedAddrs = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs } - // NewTCPNode returns a started tcp-based libp2p host. func NewTCPNode(cfg Config, key *ecdsa.PrivateKey, connGater ConnGater, - udpNode UDPNode, peers []Peer, factory p2pcfg.AddrsFactory) (host.Host, error, + udpNode *discover.UDPv5, peers, relays []Peer) (host.Host, error, ) { addrs, err := cfg.Multiaddrs() if err != nil { @@ -67,10 +59,12 @@ func NewTCPNode(cfg Config, key *ecdsa.PrivateKey, connGater ConnGater, // Limit connections to DV peers. libp2p.ConnectionGater(connGater), - libp2p.AddrsFactory(factory), + // Define p2pcfg.AddrsFactory that does not advertise + // addresses via libp2p, since we use discv5 for peer discovery. + libp2p.AddrsFactory(func([]ma.Multiaddr) []ma.Multiaddr { return nil }), libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { - return logWrapRouting(adaptDiscRouting(udpNode, peers)), nil + return logWrapRouting(adaptDiscRouting(udpNode, peers, relays)), nil }), } @@ -106,14 +100,14 @@ func logWrapRouting(fn peerRoutingFunc) peerRoutingFunc { } // adaptDiscRouting returns a function that adapts p2p routing requests to discv5 lookups. -func adaptDiscRouting(udpNode UDPNode, peers []Peer) peerRoutingFunc { +func adaptDiscRouting(udpNode *discover.UDPv5, peers, relays []Peer) peerRoutingFunc { peerMap := make(map[peer.ID]enode.Node) for _, p := range peers { peerMap[p.ID] = p.Enode } - for _, bootnode := range udpNode.Relays { - peerMap[bootnode.ID] = bootnode.Enode + for _, relay := range relays { + peerMap[relay.ID] = relay.Enode } return func(ctx context.Context, peerID peer.ID) (peer.AddrInfo, error) { @@ -140,7 +134,7 @@ func adaptDiscRouting(udpNode UDPNode, peers []Peer) peerRoutingFunc { } // Add any circuit relays - for _, relay := range udpNode.Relays { + for _, relay := range relays { if relay.Enode.TCP() == 0 { continue } diff --git a/p2p/peer.go b/p2p/peer.go index 7262186c9..07da19d1c 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -40,27 +40,6 @@ type Peer struct { Index int } -// newRelayPeer returns a new relay peer. -func newRelayPeer(nodeAddr string) (Peer, error) { - node, err := enode.Parse(enode.V4ID{}, nodeAddr) - if err != nil { - return Peer{}, errors.Wrap(err, "invalid relay address") - } - - p2pPubkey := libp2pcrypto.Secp256k1PublicKey(*node.Pubkey()) - id, err := peer.IDFromPublicKey(&p2pPubkey) - if err != nil { - return Peer{}, errors.Wrap(err, "p2p id from pubkey") - } - - return Peer{ - ENR: *node.Record(), - Enode: *node, - ID: id, - Index: -1, - }, nil -} - // NewPeer returns a new charon peer. func NewPeer(record enr.Record, index int) (Peer, error) { var pubkey enode.Secp256k1 diff --git a/p2p/relay.go b/p2p/relay.go index f29b03c60..154d685f1 100644 --- a/p2p/relay.go +++ b/p2p/relay.go @@ -19,6 +19,7 @@ import ( "context" "time" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" circuit "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client" @@ -30,6 +31,28 @@ import ( "github.com/obolnetwork/charon/app/z" ) +// NewRelays returns the libp2p circuit relays from bootnodes if enabled. +func NewRelays(conf Config, bootnodes []*enode.Node) ([]Peer, error) { + if !conf.BootnodeRelay { + return nil, nil + } else if conf.UDPBootManifest { + // Relays not supported via manifest bootnodes yet. + return nil, nil + } + + var resp []Peer + for _, bootnode := range bootnodes { + record := bootnode.Record() + p, err := NewPeer(*record, -1) + if err != nil { + return nil, err + } + resp = append(resp, p) + } + + return resp, nil +} + // NewRelayReserver returns a life cycle hook function that continuously // reserves a relay circuit until the context is closed. func NewRelayReserver(tcpNode host.Host, relay Peer) lifecycle.HookFunc {