github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/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 "net" 26 "os" 27 "strconv" 28 "strings" 29 30 "github.com/ethw3/go-ethereuma/p2p/enode" 31 "github.com/ethw3/go-ethereuma/p2p/enr" 32 "github.com/ethw3/go-ethereuma/rlp" 33 "github.com/urfave/cli/v2" 34 ) 35 36 var fileFlag = &cli.StringFlag{Name: "file"} 37 38 var enrdumpCommand = &cli.Command{ 39 Name: "enrdump", 40 Usage: "Pretty-prints node records", 41 Action: enrdump, 42 Flags: []cli.Flag{ 43 fileFlag, 44 }, 45 } 46 47 func enrdump(ctx *cli.Context) error { 48 var source string 49 if file := ctx.String(fileFlag.Name); file != "" { 50 if ctx.NArg() != 0 { 51 return fmt.Errorf("can't dump record from command-line argument in -file mode") 52 } 53 var b []byte 54 var err error 55 if file == "-" { 56 b, err = io.ReadAll(os.Stdin) 57 } else { 58 b, err = os.ReadFile(file) 59 } 60 if err != nil { 61 return err 62 } 63 source = string(b) 64 } else if ctx.NArg() == 1 { 65 source = ctx.Args().First() 66 } else { 67 return fmt.Errorf("need record as argument") 68 } 69 70 r, err := parseRecord(source) 71 if err != nil { 72 return fmt.Errorf("INVALID: %v", err) 73 } 74 dumpRecord(os.Stdout, r) 75 return nil 76 } 77 78 // dumpRecord creates a human-readable description of the given node record. 79 func dumpRecord(out io.Writer, r *enr.Record) { 80 n, err := enode.New(enode.ValidSchemes, r) 81 if err != nil { 82 fmt.Fprintf(out, "INVALID: %v\n", err) 83 } else { 84 fmt.Fprintf(out, "Node ID: %v\n", n.ID()) 85 dumpNodeURL(out, n) 86 } 87 kv := r.AppendElements(nil)[1:] 88 fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) 89 fmt.Fprint(out, dumpRecordKV(kv, 2)) 90 } 91 92 func dumpNodeURL(out io.Writer, n *enode.Node) { 93 var key enode.Secp256k1 94 if n.Load(&key) != nil { 95 return // no secp256k1 public key 96 } 97 fmt.Fprintf(out, "URLv4: %s\n", n.URLv4()) 98 } 99 100 func dumpRecordKV(kv []interface{}, indent int) string { 101 // Determine the longest key name for alignment. 102 var out string 103 var longestKey = 0 104 for i := 0; i < len(kv); i += 2 { 105 key := kv[i].(string) 106 if len(key) > longestKey { 107 longestKey = len(key) 108 } 109 } 110 // Print the keys, invoking formatters for known keys. 111 for i := 0; i < len(kv); i += 2 { 112 key := kv[i].(string) 113 val := kv[i+1].(rlp.RawValue) 114 pad := longestKey - len(key) 115 out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) 116 formatter := attrFormatters[key] 117 if formatter == nil { 118 formatter = formatAttrRaw 119 } 120 fmtval, ok := formatter(val) 121 if ok { 122 out += fmtval + "\n" 123 } else { 124 out += hex.EncodeToString(val) + " (!)\n" 125 } 126 } 127 return out 128 } 129 130 // parseNode parses a node record and verifies its signature. 131 func parseNode(source string) (*enode.Node, error) { 132 if strings.HasPrefix(source, "enode://") { 133 return enode.ParseV4(source) 134 } 135 r, err := parseRecord(source) 136 if err != nil { 137 return nil, err 138 } 139 return enode.New(enode.ValidSchemes, r) 140 } 141 142 // parseRecord parses a node record from hex, base64, or raw binary input. 143 func parseRecord(source string) (*enr.Record, error) { 144 bin := []byte(source) 145 if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { 146 bin = d 147 } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { 148 bin = d 149 } 150 var r enr.Record 151 err := rlp.DecodeBytes(bin, &r) 152 return &r, err 153 } 154 155 func decodeRecordHex(b []byte) ([]byte, bool) { 156 if bytes.HasPrefix(b, []byte("0x")) { 157 b = b[2:] 158 } 159 dec := make([]byte, hex.DecodedLen(len(b))) 160 _, err := hex.Decode(dec, b) 161 return dec, err == nil 162 } 163 164 func decodeRecordBase64(b []byte) ([]byte, bool) { 165 if bytes.HasPrefix(b, []byte("enr:")) { 166 b = b[4:] 167 } 168 dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) 169 n, err := base64.RawURLEncoding.Decode(dec, b) 170 return dec[:n], err == nil 171 } 172 173 // attrFormatters contains formatting functions for well-known ENR keys. 174 var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ 175 "id": formatAttrString, 176 "ip": formatAttrIP, 177 "ip6": formatAttrIP, 178 "tcp": formatAttrUint, 179 "tcp6": formatAttrUint, 180 "udp": formatAttrUint, 181 "udp6": formatAttrUint, 182 } 183 184 func formatAttrRaw(v rlp.RawValue) (string, bool) { 185 s := hex.EncodeToString(v) 186 return s, true 187 } 188 189 func formatAttrString(v rlp.RawValue) (string, bool) { 190 content, _, err := rlp.SplitString(v) 191 return strconv.Quote(string(content)), err == nil 192 } 193 194 func formatAttrIP(v rlp.RawValue) (string, bool) { 195 content, _, err := rlp.SplitString(v) 196 if err != nil || len(content) != 4 && len(content) != 6 { 197 return "", false 198 } 199 return net.IP(content).String(), true 200 } 201 202 func formatAttrUint(v rlp.RawValue) (string, bool) { 203 var x uint64 204 if err := rlp.DecodeBytes(v, &x); err != nil { 205 return "", false 206 } 207 return strconv.FormatUint(x, 10), true 208 }