From 16c5d96e6a0db35535f04e994cd447672a5e2ffc Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Mon, 19 Aug 2019 01:24:20 +0530 Subject: [PATCH] Change BootNode to use Discv5 instead of Kademlia (#3203) * add new test * specify ecdsa keygen * skip test * fix ref * comment again * fix test and clean up * gaz * change to another format * Apply suggestions from code review Co-Authored-By: Preston Van Loon * fix docker build * add close --- tools/bootnode/BUILD.bazel | 31 ++++++------ tools/bootnode/bootnode.go | 87 ++++++++++++++++----------------- tools/bootnode/bootnode_test.go | 55 +++++++++++++++++++++ 3 files changed, 113 insertions(+), 60 deletions(-) create mode 100644 tools/bootnode/bootnode_test.go diff --git a/tools/bootnode/BUILD.bazel b/tools/bootnode/BUILD.bazel index e0b7be55ff..6992e3c594 100644 --- a/tools/bootnode/BUILD.bazel +++ b/tools/bootnode/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") load("@io_bazel_rules_docker//go:image.bzl", "go_image") load("@io_bazel_rules_docker//container:container.bzl", "container_bundle") load("@io_bazel_rules_docker//contrib:push-all.bzl", "docker_push") @@ -10,15 +10,10 @@ go_library( visibility = ["//visibility:private"], deps = [ "//shared/version:go_default_library", - "@com_github_ipfs_go_datastore//:go_default_library", - "@com_github_ipfs_go_datastore//sync:go_default_library", + "@com_github_ethereum_go_ethereum//crypto:go_default_library", + "@com_github_ethereum_go_ethereum//p2p/discv5:go_default_library", "@com_github_ipfs_go_log//:go_default_library", - "@com_github_libp2p_go_libp2p//:go_default_library", "@com_github_libp2p_go_libp2p_crypto//:go_default_library", - "@com_github_libp2p_go_libp2p_kad_dht//:go_default_library", - "@com_github_libp2p_go_libp2p_kad_dht//opts:go_default_library", - "@com_github_libp2p_go_libp2p_protocol//:go_default_library", - "@com_github_multiformats_go_multiaddr//:go_default_library", "@org_uber_go_automaxprocs//:go_default_library", ], ) @@ -36,15 +31,10 @@ go_image( visibility = ["//visibility:private"], deps = [ "//shared/version:go_default_library", - "@com_github_ipfs_go_datastore//:go_default_library", - "@com_github_ipfs_go_datastore//sync:go_default_library", + "@com_github_ethereum_go_ethereum//crypto:go_default_library", + "@com_github_ethereum_go_ethereum//p2p/discv5:go_default_library", "@com_github_ipfs_go_log//:go_default_library", - "@com_github_libp2p_go_libp2p//:go_default_library", "@com_github_libp2p_go_libp2p_crypto//:go_default_library", - "@com_github_libp2p_go_libp2p_kad_dht//:go_default_library", - "@com_github_libp2p_go_libp2p_kad_dht//opts:go_default_library", - "@com_github_libp2p_go_libp2p_protocol//:go_default_library", - "@com_github_multiformats_go_multiaddr//:go_default_library", "@org_uber_go_automaxprocs//:go_default_library", ], ) @@ -69,3 +59,14 @@ docker_push( bundle = ":image_bundle", tags = ["manual"], ) + +go_test( + name = "go_default_test", + srcs = ["bootnode_test.go"], + embed = [":go_default_library"], + deps = [ + "//shared/iputils:go_default_library", + "@com_github_ethereum_go_ethereum//p2p/discv5:go_default_library", + "@org_uber_go_automaxprocs//:go_default_library", + ], +) diff --git a/tools/bootnode/bootnode.go b/tools/bootnode/bootnode.go index 240ecf0f0d..c17da5dcc9 100644 --- a/tools/bootnode/bootnode.go +++ b/tools/bootnode/bootnode.go @@ -1,7 +1,7 @@ /** * Bootnode * - * A simple peer Kademlia distributed hash table (DHT) service for peer + * A node which implements the DiscoveryV5 protocol for peer * discovery. The purpose of this service is to provide a starting point for * newly connected services to find other peers outside of their network. * @@ -10,19 +10,18 @@ package main import ( - "context" + "crypto/ecdsa" + "crypto/rand" + "crypto/x509" "flag" "fmt" + "net" + + curve "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discv5" - ds "github.com/ipfs/go-datastore" - dsync "github.com/ipfs/go-datastore/sync" logging "github.com/ipfs/go-log" - libp2p "github.com/libp2p/go-libp2p" crypto "github.com/libp2p/go-libp2p-crypto" - kaddht "github.com/libp2p/go-libp2p-kad-dht" - dhtopts "github.com/libp2p/go-libp2p-kad-dht/opts" - protocol "github.com/libp2p/go-libp2p-protocol" - ma "github.com/multiformats/go-multiaddr" "github.com/prysmaticlabs/prysm/shared/version" _ "go.uber.org/automaxprocs" ) @@ -35,7 +34,8 @@ var ( log = logging.Logger("prysm-bootnode") ) -const dhtProtocol = "/prysm/0.0.0/dht" +// ECDSACurve is the default ecdsa curve used(secpk2561) +var ECDSACurve = curve.S256() func main() { flag.Parse() @@ -46,56 +46,53 @@ func main() { logging.SetDebugLogging() } - listen, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *port)) - if err != nil { - log.Fatalf("Failed to construct new multiaddress. %v", err) - } + defaultIP := "0.0.0.0" - opts := []libp2p.Option{ - libp2p.ListenAddrs(listen), - } - opts = addPrivateKeyOpt(opts) + privKey := extractPrivateKey() + listener := createListener(defaultIP, *port, privKey) - ctx := context.Background() - - host, err := libp2p.New(ctx, opts...) - if err != nil { - log.Fatalf("Failed to create new host. %v", err) - } - - dopts := []dhtopts.Option{ - dhtopts.Datastore(dsync.MutexWrap(ds.NewMapDatastore())), - dhtopts.Protocols( - protocol.ID(dhtProtocol), - ), - } - - dht, err := kaddht.New(ctx, host, dopts...) - if err != nil { - log.Fatalf("Failed to create new dht: %v", err) - } - if err := dht.Bootstrap(context.Background()); err != nil { - log.Fatalf("Failed to bootstrap DHT. %v", err) - } - - fmt.Printf("Running bootnode: /ip4/0.0.0.0/tcp/%d/p2p/%s\n", *port, host.ID().Pretty()) + node := listener.Self() + fmt.Printf("Running bootnode: /ip4/%s/udp/%d/discv5/%s\n", node.IP.String(), node.UDP, node.ID.String()) select {} } -func addPrivateKeyOpt(opts []libp2p.Option) []libp2p.Option { +func createListener(ipAddr string, port int, privKey *ecdsa.PrivateKey) *discv5.Network { + udpAddr := &net.UDPAddr{ + IP: net.ParseIP(ipAddr), + Port: port, + } + conn, err := net.ListenUDP("udp4", udpAddr) + if err != nil { + log.Fatal(err) + } + + network, err := discv5.ListenUDP(privKey, conn, "", nil) + if err != nil { + log.Fatal(err) + } + return network +} + +func extractPrivateKey() *ecdsa.PrivateKey { + var privKey *ecdsa.PrivateKey + var err error if *privateKey != "" { b, err := crypto.ConfigDecodeKey(*privateKey) if err != nil { panic(err) } - pk, err := crypto.UnmarshalPrivateKey(b) + privKey, err = x509.ParseECPrivateKey(b) if err != nil { panic(err) } - opts = append(opts, libp2p.Identity(pk)) + } else { + privKey, err = ecdsa.GenerateKey(ECDSACurve, rand.Reader) + if err != nil { + panic(err) + } log.Warning("No private key was provided. Using default/random private key") } - return opts + return privKey } diff --git a/tools/bootnode/bootnode_test.go b/tools/bootnode/bootnode_test.go new file mode 100644 index 0000000000..b4416b4ca9 --- /dev/null +++ b/tools/bootnode/bootnode_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/prysmaticlabs/prysm/shared/iputils" + _ "go.uber.org/automaxprocs" +) + +func TestBootnode_OK(t *testing.T) { + ipAddr, err := iputils.ExternalIPv4() + if err != nil { + t.Fatal(err) + } + privKey := extractPrivateKey() + listener := createListener(ipAddr, 4000, privKey) + defer listener.Close() + + privKey = extractPrivateKey() + listener2 := createListener(ipAddr, 4001, privKey) + defer listener.Close() + + err = listener.SetFallbackNodes([]*discv5.Node{listener2.Self()}) + if err != nil { + t.Fatal(err) + } + + err = listener2.SetFallbackNodes([]*discv5.Node{listener.Self()}) + if err != nil { + t.Fatal(err) + } + + // test that both the nodes have the other peer stored in their local table. + listenerNode := listener.Self() + listenerNode2 := listener2.Self() + + nodes := listener.Lookup(listenerNode2.ID) + if len(nodes) != 2 { + t.Errorf("Length of nodes stored in table is not expected. Wanted %d but got %d", 2, len(nodes)) + + } + if nodes[0].ID != listenerNode2.ID { + t.Errorf("Wanted node ID of %s but got %s", listenerNode2.ID, nodes[1].ID) + } + + nodes = listener2.Lookup(listenerNode.ID) + if len(nodes) != 2 { + t.Errorf("Length of nodes stored in table is not expected. Wanted %d but got %d", 2, len(nodes)) + + } + if nodes[0].ID != listenerNode.ID { + t.Errorf("Wanted node ID of %s but got %s", listenerNode.ID, nodes[1].ID) + } +}