github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/cmd/devp2p/enrcmd.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/base64" 22 "encoding/hex" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "net" 27 "os" 28 "strconv" 29 "strings" 30 31 "github.com/kisexp/xdchain/p2p/enode" 32 "github.com/kisexp/xdchain/p2p/enr" 33 "github.com/kisexp/xdchain/rlp" 34 "gopkg.in/urfave/cli.v1" 35 ) 36 37 var enrdumpCommand = cli.Command{ 38 Name: "enrdump", 39 Usage: "Pretty-prints node records", 40 Action: enrdump, 41 Flags: []cli.Flag{ 42 cli.StringFlag{Name: "file"}, 43 }, 44 } 45 46 func enrdump(ctx *cli.Context) error { 47 var source string 48 if file := ctx.String("file"); file != "" { 49 if ctx.NArg() != 0 { 50 return fmt.Errorf("can't dump record from command-line argument in -file mode") 51 } 52 var b []byte 53 var err error 54 if file == "-" { 55 b, err = ioutil.ReadAll(os.Stdin) 56 } else { 57 b, err = ioutil.ReadFile(file) 58 } 59 if err != nil { 60 return err 61 } 62 source = string(b) 63 } else if ctx.NArg() == 1 { 64 source = ctx.Args()[0] 65 } else { 66 return fmt.Errorf("need record as argument") 67 } 68 69 r, err := parseRecord(source) 70 if err != nil { 71 return fmt.Errorf("INVALID: %v", err) 72 } 73 dumpRecord(os.Stdout, r) 74 return nil 75 } 76 77 // dumpRecord creates a human-readable description of the given node record. 78 func dumpRecord(out io.Writer, r *enr.Record) { 79 n, err := enode.New(enode.ValidSchemes, r) 80 if err != nil { 81 fmt.Fprintf(out, "INVALID: %v\n", err) 82 } else { 83 fmt.Fprintf(out, "Node ID: %v\n", n.ID()) 84 dumpNodeURL(out, n) 85 } 86 kv := r.AppendElements(nil)[1:] 87 fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) 88 fmt.Fprint(out, dumpRecordKV(kv, 2)) 89 } 90 91 func dumpNodeURL(out io.Writer, n *enode.Node) { 92 var key enode.Secp256k1 93 if n.Load(&key) != nil { 94 return // no secp256k1 public key 95 } 96 fmt.Fprintf(out, "URLv4: %s\n", n.URLv4()) 97 } 98 99 func dumpRecordKV(kv []interface{}, indent int) string { 100 // Determine the longest key name for alignment. 101 var out string 102 var longestKey = 0 103 for i := 0; i < len(kv); i += 2 { 104 key := kv[i].(string) 105 if len(key) > longestKey { 106 longestKey = len(key) 107 } 108 } 109 // Print the keys, invoking formatters for known keys. 110 for i := 0; i < len(kv); i += 2 { 111 key := kv[i].(string) 112 val := kv[i+1].(rlp.RawValue) 113 pad := longestKey - len(key) 114 out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) 115 formatter := attrFormatters[key] 116 if formatter == nil { 117 formatter = formatAttrRaw 118 } 119 fmtval, ok := formatter(val) 120 if ok { 121 out += fmtval + "\n" 122 } else { 123 out += hex.EncodeToString(val) + " (!)\n" 124 } 125 } 126 return out 127 } 128 129 // parseNode parses a node record and verifies its signature. 130 func parseNode(source string) (*enode.Node, error) { 131 if strings.HasPrefix(source, "enode://") { 132 return enode.ParseV4(source) 133 } 134 r, err := parseRecord(source) 135 if err != nil { 136 return nil, err 137 } 138 return enode.New(enode.ValidSchemes, r) 139 } 140 141 // parseRecord parses a node record from hex, base64, or raw binary input. 142 func parseRecord(source string) (*enr.Record, error) { 143 bin := []byte(source) 144 if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { 145 bin = d 146 } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { 147 bin = d 148 } 149 var r enr.Record 150 err := rlp.DecodeBytes(bin, &r) 151 return &r, err 152 } 153 154 func decodeRecordHex(b []byte) ([]byte, bool) { 155 if bytes.HasPrefix(b, []byte("0x")) { 156 b = b[2:] 157 } 158 dec := make([]byte, hex.DecodedLen(len(b))) 159 _, err := hex.Decode(dec, b) 160 return dec, err == nil 161 } 162 163 func decodeRecordBase64(b []byte) ([]byte, bool) { 164 if bytes.HasPrefix(b, []byte("enr:")) { 165 b = b[4:] 166 } 167 dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) 168 n, err := base64.RawURLEncoding.Decode(dec, b) 169 return dec[:n], err == nil 170 } 171 172 // attrFormatters contains formatting functions for well-known ENR keys. 173 var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ 174 "id": formatAttrString, 175 "ip": formatAttrIP, 176 "ip6": formatAttrIP, 177 "tcp": formatAttrUint, 178 "tcp6": formatAttrUint, 179 "udp": formatAttrUint, 180 "udp6": formatAttrUint, 181 } 182 183 func formatAttrRaw(v rlp.RawValue) (string, bool) { 184 s := hex.EncodeToString(v) 185 return s, true 186 } 187 188 func formatAttrString(v rlp.RawValue) (string, bool) { 189 content, _, err := rlp.SplitString(v) 190 return strconv.Quote(string(content)), err == nil 191 } 192 193 func formatAttrIP(v rlp.RawValue) (string, bool) { 194 content, _, err := rlp.SplitString(v) 195 if err != nil || len(content) != 4 && len(content) != 6 { 196 return "", false 197 } 198 return net.IP(content).String(), true 199 } 200 201 func formatAttrUint(v rlp.RawValue) (string, bool) { 202 var x uint64 203 if err := rlp.DecodeBytes(v, &x); err != nil { 204 return "", false 205 } 206 return strconv.FormatUint(x, 10), true 207 }