cmd/devp2p: add devp2p debug tool (#19657)
* p2p/discover: export Ping and RequestENR These two are useful for checking the status of a node. * cmd/devp2p: add devp2p debug tool This is a new tool for debugging p2p issues. It supports a few basic tasks for now, but many more things can and will be added in the near future. devp2p enrdump -- prints ENRs readably devp2p discv4 ping -- checks if a node is up devp2p discv4 requestenr -- gets a node's record devp2p discv4 resolve -- finds a node through the DHT
This commit is contained in:
		
							parent
							
								
									1b15c6da33
								
							
						
					
					
						commit
						896322bf88
					
				
							
								
								
									
										166
									
								
								cmd/devp2p/discv4cmd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								cmd/devp2p/discv4cmd.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | ||||
| // Copyright 2019 The go-ethereum Authors
 | ||||
| // This file is part of go-ethereum.
 | ||||
| //
 | ||||
| // go-ethereum 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.
 | ||||
| //
 | ||||
| // go-ethereum 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 go-ethereum. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"github.com/ethereum/go-ethereum/p2p/discover" | ||||
| 	"github.com/ethereum/go-ethereum/p2p/enode" | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| 	"gopkg.in/urfave/cli.v1" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	discv4Command = cli.Command{ | ||||
| 		Name:  "discv4", | ||||
| 		Usage: "Node Discovery v4 tools", | ||||
| 		Subcommands: []cli.Command{ | ||||
| 			discv4PingCommand, | ||||
| 			discv4RequestRecordCommand, | ||||
| 			discv4ResolveCommand, | ||||
| 		}, | ||||
| 	} | ||||
| 	discv4PingCommand = cli.Command{ | ||||
| 		Name:   "ping", | ||||
| 		Usage:  "Sends ping to a node", | ||||
| 		Action: discv4Ping, | ||||
| 	} | ||||
| 	discv4RequestRecordCommand = cli.Command{ | ||||
| 		Name:   "requestenr", | ||||
| 		Usage:  "Requests a node record using EIP-868 enrRequest", | ||||
| 		Action: discv4RequestRecord, | ||||
| 	} | ||||
| 	discv4ResolveCommand = cli.Command{ | ||||
| 		Name:   "resolve", | ||||
| 		Usage:  "Finds a node in the DHT", | ||||
| 		Action: discv4Resolve, | ||||
| 		Flags:  []cli.Flag{bootnodesFlag}, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| var bootnodesFlag = cli.StringFlag{ | ||||
| 	Name:  "bootnodes", | ||||
| 	Usage: "Comma separated nodes used for bootstrapping", | ||||
| } | ||||
| 
 | ||||
| func discv4Ping(ctx *cli.Context) error { | ||||
| 	n, disc, err := getNodeArgAndStartV4(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer disc.Close() | ||||
| 
 | ||||
| 	start := time.Now() | ||||
| 	if err := disc.Ping(n); err != nil { | ||||
| 		return fmt.Errorf("node didn't respond: %v", err) | ||||
| 	} | ||||
| 	fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func discv4RequestRecord(ctx *cli.Context) error { | ||||
| 	n, disc, err := getNodeArgAndStartV4(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer disc.Close() | ||||
| 
 | ||||
| 	respN, err := disc.RequestENR(n) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("can't retrieve record: %v", err) | ||||
| 	} | ||||
| 	fmt.Println(respN.String()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func discv4Resolve(ctx *cli.Context) error { | ||||
| 	n, disc, err := getNodeArgAndStartV4(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer disc.Close() | ||||
| 
 | ||||
| 	fmt.Println(disc.Resolve(n).String()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func getNodeArgAndStartV4(ctx *cli.Context) (*enode.Node, *discover.UDPv4, error) { | ||||
| 	if ctx.NArg() != 1 { | ||||
| 		return nil, nil, fmt.Errorf("missing node as command-line argument") | ||||
| 	} | ||||
| 	n, err := parseNode(ctx.Args()[0]) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	var bootnodes []*enode.Node | ||||
| 	if commandHasFlag(ctx, bootnodesFlag) { | ||||
| 		bootnodes, err = parseBootnodes(ctx) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	disc, err := startV4(bootnodes) | ||||
| 	return n, disc, err | ||||
| } | ||||
| 
 | ||||
| func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { | ||||
| 	s := params.RinkebyBootnodes | ||||
| 	if ctx.IsSet(bootnodesFlag.Name) { | ||||
| 		s = strings.Split(ctx.String(bootnodesFlag.Name), ",") | ||||
| 	} | ||||
| 	nodes := make([]*enode.Node, len(s)) | ||||
| 	var err error | ||||
| 	for i, record := range s { | ||||
| 		nodes[i], err = parseNode(record) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("invalid bootstrap node: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nodes, nil | ||||
| } | ||||
| 
 | ||||
| // commandHasFlag returns true if the current command supports the given flag.
 | ||||
| func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool { | ||||
| 	flags := ctx.FlagNames() | ||||
| 	sort.Strings(flags) | ||||
| 	i := sort.SearchStrings(flags, flag.GetName()) | ||||
| 	return i != len(flags) && flags[i] == flag.GetName() | ||||
| } | ||||
| 
 | ||||
| // startV4 starts an ephemeral discovery V4 node.
 | ||||
| func startV4(bootnodes []*enode.Node) (*discover.UDPv4, error) { | ||||
| 	var cfg discover.Config | ||||
| 	cfg.Bootnodes = bootnodes | ||||
| 	cfg.PrivateKey, _ = crypto.GenerateKey() | ||||
| 	db, _ := enode.OpenDB("") | ||||
| 	ln := enode.NewLocalNode(db, cfg.PrivateKey) | ||||
| 
 | ||||
| 	socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	addr := socket.LocalAddr().(*net.UDPAddr) | ||||
| 	ln.SetFallbackIP(net.IP{127, 0, 0, 1}) | ||||
| 	ln.SetFallbackUDP(addr.Port) | ||||
| 	return discover.ListenUDP(socket, ln, cfg) | ||||
| } | ||||
							
								
								
									
										198
									
								
								cmd/devp2p/enrcmd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								cmd/devp2p/enrcmd.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| // Copyright 2019 The go-ethereum Authors
 | ||||
| // This file is part of go-ethereum.
 | ||||
| //
 | ||||
| // go-ethereum 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.
 | ||||
| //
 | ||||
| // go-ethereum 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 go-ethereum. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/p2p/enode" | ||||
| 	"github.com/ethereum/go-ethereum/p2p/enr" | ||||
| 	"github.com/ethereum/go-ethereum/rlp" | ||||
| 	"gopkg.in/urfave/cli.v1" | ||||
| ) | ||||
| 
 | ||||
| var enrdumpCommand = cli.Command{ | ||||
| 	Name:   "enrdump", | ||||
| 	Usage:  "Pretty-prints node records", | ||||
| 	Action: enrdump, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		cli.StringFlag{Name: "file"}, | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func enrdump(ctx *cli.Context) error { | ||||
| 	var source string | ||||
| 	if file := ctx.String("file"); file != "" { | ||||
| 		if ctx.NArg() != 0 { | ||||
| 			return fmt.Errorf("can't dump record from command-line argument in -file mode") | ||||
| 		} | ||||
| 		var b []byte | ||||
| 		var err error | ||||
| 		if file == "-" { | ||||
| 			b, err = ioutil.ReadAll(os.Stdin) | ||||
| 		} else { | ||||
| 			b, err = ioutil.ReadFile(file) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		source = string(b) | ||||
| 	} else if ctx.NArg() == 1 { | ||||
| 		source = ctx.Args()[0] | ||||
| 	} else { | ||||
| 		return fmt.Errorf("need record as argument") | ||||
| 	} | ||||
| 
 | ||||
| 	r, err := parseRecord(source) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("INVALID: %v", err) | ||||
| 	} | ||||
| 	fmt.Print(dumpRecord(r)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // dumpRecord creates a human-readable description of the given node record.
 | ||||
| func dumpRecord(r *enr.Record) string { | ||||
| 	out := new(bytes.Buffer) | ||||
| 	if n, err := enode.New(enode.ValidSchemes, r); err != nil { | ||||
| 		fmt.Fprintf(out, "INVALID: %v\n", err) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(out, "Node ID: %v\n", n.ID()) | ||||
| 	} | ||||
| 	kv := r.AppendElements(nil)[1:] | ||||
| 	fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) | ||||
| 	fmt.Fprint(out, dumpRecordKV(kv, 2)) | ||||
| 	return out.String() | ||||
| } | ||||
| 
 | ||||
| func dumpRecordKV(kv []interface{}, indent int) string { | ||||
| 	// Determine the longest key name for alignment.
 | ||||
| 	var out string | ||||
| 	var longestKey = 0 | ||||
| 	for i := 0; i < len(kv); i += 2 { | ||||
| 		key := kv[i].(string) | ||||
| 		if len(key) > longestKey { | ||||
| 			longestKey = len(key) | ||||
| 		} | ||||
| 	} | ||||
| 	// Print the keys, invoking formatters for known keys.
 | ||||
| 	for i := 0; i < len(kv); i += 2 { | ||||
| 		key := kv[i].(string) | ||||
| 		val := kv[i+1].(rlp.RawValue) | ||||
| 		pad := longestKey - len(key) | ||||
| 		out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) | ||||
| 		formatter := attrFormatters[key] | ||||
| 		if formatter == nil { | ||||
| 			formatter = formatAttrRaw | ||||
| 		} | ||||
| 		fmtval, ok := formatter(val) | ||||
| 		if ok { | ||||
| 			out += fmtval + "\n" | ||||
| 		} else { | ||||
| 			out += hex.EncodeToString(val) + " (!)\n" | ||||
| 		} | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| // parseNode parses a node record and verifies its signature.
 | ||||
| func parseNode(source string) (*enode.Node, error) { | ||||
| 	if strings.HasPrefix(source, "enode://") { | ||||
| 		return enode.ParseV4(source) | ||||
| 	} | ||||
| 	r, err := parseRecord(source) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return enode.New(enode.ValidSchemes, r) | ||||
| } | ||||
| 
 | ||||
| // parseRecord parses a node record from hex, base64, or raw binary input.
 | ||||
| func parseRecord(source string) (*enr.Record, error) { | ||||
| 	bin := []byte(source) | ||||
| 	if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { | ||||
| 		bin = d | ||||
| 	} else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { | ||||
| 		bin = d | ||||
| 	} | ||||
| 	var r enr.Record | ||||
| 	err := rlp.DecodeBytes(bin, &r) | ||||
| 	return &r, err | ||||
| } | ||||
| 
 | ||||
| func decodeRecordHex(b []byte) ([]byte, bool) { | ||||
| 	if bytes.HasPrefix(b, []byte("0x")) { | ||||
| 		b = b[2:] | ||||
| 	} | ||||
| 	dec := make([]byte, hex.DecodedLen(len(b))) | ||||
| 	_, err := hex.Decode(dec, b) | ||||
| 	return dec, err == nil | ||||
| } | ||||
| 
 | ||||
| func decodeRecordBase64(b []byte) ([]byte, bool) { | ||||
| 	if bytes.HasPrefix(b, []byte("enr:")) { | ||||
| 		b = b[4:] | ||||
| 	} | ||||
| 	dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) | ||||
| 	n, err := base64.RawURLEncoding.Decode(dec, b) | ||||
| 	return dec[:n], err == nil | ||||
| } | ||||
| 
 | ||||
| // attrFormatters contains formatting functions for well-known ENR keys.
 | ||||
| var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ | ||||
| 	"id":   formatAttrString, | ||||
| 	"ip":   formatAttrIP, | ||||
| 	"ip6":  formatAttrIP, | ||||
| 	"tcp":  formatAttrUint, | ||||
| 	"tcp6": formatAttrUint, | ||||
| 	"udp":  formatAttrUint, | ||||
| 	"udp6": formatAttrUint, | ||||
| } | ||||
| 
 | ||||
| func formatAttrRaw(v rlp.RawValue) (string, bool) { | ||||
| 	s := hex.EncodeToString(v) | ||||
| 	return s, true | ||||
| } | ||||
| 
 | ||||
| func formatAttrString(v rlp.RawValue) (string, bool) { | ||||
| 	content, _, err := rlp.SplitString(v) | ||||
| 	return strconv.Quote(string(content)), err == nil | ||||
| } | ||||
| 
 | ||||
| func formatAttrIP(v rlp.RawValue) (string, bool) { | ||||
| 	content, _, err := rlp.SplitString(v) | ||||
| 	if err != nil || len(content) != 4 && len(content) != 6 { | ||||
| 		return "", false | ||||
| 	} | ||||
| 	return net.IP(content).String(), true | ||||
| } | ||||
| 
 | ||||
| func formatAttrUint(v rlp.RawValue) (string, bool) { | ||||
| 	var x uint64 | ||||
| 	if err := rlp.DecodeBytes(v, &x); err != nil { | ||||
| 		return "", false | ||||
| 	} | ||||
| 	return strconv.FormatUint(x, 10), true | ||||
| } | ||||
							
								
								
									
										68
									
								
								cmd/devp2p/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								cmd/devp2p/main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| // Copyright 2019 The go-ethereum Authors
 | ||||
| // This file is part of go-ethereum.
 | ||||
| //
 | ||||
| // go-ethereum 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.
 | ||||
| //
 | ||||
| // go-ethereum 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 go-ethereum. If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/internal/debug" | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| 	"gopkg.in/urfave/cli.v1" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// Git information set by linker when building with ci.go.
 | ||||
| 	gitCommit string | ||||
| 	gitDate   string | ||||
| 	app       = &cli.App{ | ||||
| 		Name:        filepath.Base(os.Args[0]), | ||||
| 		Usage:       "go-ethereum devp2p tool", | ||||
| 		Version:     params.VersionWithCommit(gitCommit, gitDate), | ||||
| 		Writer:      os.Stdout, | ||||
| 		HideVersion: true, | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	// Set up the CLI app.
 | ||||
| 	app.Flags = append(app.Flags, debug.Flags...) | ||||
| 	app.Before = func(ctx *cli.Context) error { | ||||
| 		return debug.Setup(ctx, "") | ||||
| 	} | ||||
| 	app.After = func(ctx *cli.Context) error { | ||||
| 		debug.Exit() | ||||
| 		return nil | ||||
| 	} | ||||
| 	app.CommandNotFound = func(ctx *cli.Context, cmd string) { | ||||
| 		fmt.Fprintf(os.Stderr, "No such command: %s\n", cmd) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	// Add subcommands.
 | ||||
| 	app.Commands = []cli.Command{ | ||||
| 		enrdumpCommand, | ||||
| 		discv4Command, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	if err := app.Run(os.Args); err != nil { | ||||
| 		fmt.Fprintln(os.Stderr, err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
| @ -85,10 +85,10 @@ type Table struct { | ||||
| // transport is implemented by the UDP transports.
 | ||||
| type transport interface { | ||||
| 	Self() *enode.Node | ||||
| 	RequestENR(*enode.Node) (*enode.Node, error) | ||||
| 	lookupRandom() []*enode.Node | ||||
| 	lookupSelf() []*enode.Node | ||||
| 	ping(*enode.Node) (seq uint64, err error) | ||||
| 	requestENR(*enode.Node) (*enode.Node, error) | ||||
| } | ||||
| 
 | ||||
| // bucket contains nodes, ordered by their last activity. the entry
 | ||||
| @ -344,7 +344,7 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { | ||||
| 
 | ||||
| 	// Also fetch record if the node replied and returned a higher sequence number.
 | ||||
| 	if last.Seq() < remoteSeq { | ||||
| 		n, err := tab.net.requestENR(unwrapNode(last)) | ||||
| 		n, err := tab.net.RequestENR(unwrapNode(last)) | ||||
| 		if err != nil { | ||||
| 			tab.log.Debug("ENR request failed", "id", last.ID(), "addr", last.addr(), "err", err) | ||||
| 		} else { | ||||
|  | ||||
| @ -145,7 +145,7 @@ func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { | ||||
| } | ||||
| 
 | ||||
| // requestENR simulates an ENR request.
 | ||||
| func (t *pingRecorder) requestENR(n *enode.Node) (*enode.Node, error) { | ||||
| func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) { | ||||
| 	t.mu.Lock() | ||||
| 	defer t.mu.Unlock() | ||||
| 
 | ||||
|  | ||||
| @ -415,13 +415,13 @@ func (t *UDPv4) lookupWorker(n *node, targetKey encPubkey, reply chan<- []*node) | ||||
| // version of the node record for it. It returns n if the node could not be resolved.
 | ||||
| func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { | ||||
| 	// Try asking directly. This works if the node is still responding on the endpoint we have.
 | ||||
| 	if rn, err := t.requestENR(n); err == nil { | ||||
| 	if rn, err := t.RequestENR(n); err == nil { | ||||
| 		return rn | ||||
| 	} | ||||
| 	// Check table for the ID, we might have a newer version there.
 | ||||
| 	if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() { | ||||
| 		n = intable | ||||
| 		if rn, err := t.requestENR(n); err == nil { | ||||
| 		if rn, err := t.RequestENR(n); err == nil { | ||||
| 			return rn | ||||
| 		} | ||||
| 	} | ||||
| @ -433,7 +433,7 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node { | ||||
| 	result := t.LookupPubkey((*ecdsa.PublicKey)(&key)) | ||||
| 	for _, rn := range result { | ||||
| 		if rn.ID() == n.ID() { | ||||
| 			if rn, err := t.requestENR(rn); err == nil { | ||||
| 			if rn, err := t.RequestENR(rn); err == nil { | ||||
| 				return rn | ||||
| 			} | ||||
| 		} | ||||
| @ -447,6 +447,12 @@ func (t *UDPv4) ourEndpoint() rpcEndpoint { | ||||
| 	return makeEndpoint(a, uint16(n.TCP())) | ||||
| } | ||||
| 
 | ||||
| // Ping sends a ping message to the given node.
 | ||||
| func (t *UDPv4) Ping(n *enode.Node) error { | ||||
| 	_, err := t.ping(n) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ping sends a ping message to the given node and waits for a reply.
 | ||||
| func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { | ||||
| 	rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) | ||||
| @ -521,8 +527,8 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ( | ||||
| 	return nodes, <-rm.errc | ||||
| } | ||||
| 
 | ||||
| // requestENR sends enrRequest to the given node and waits for a response.
 | ||||
| func (t *UDPv4) requestENR(n *enode.Node) (*enode.Node, error) { | ||||
| // RequestENR sends enrRequest to the given node and waits for a response.
 | ||||
| func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { | ||||
| 	addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()} | ||||
| 	t.ensureBond(n.ID(), addr) | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user