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