github.com/theQRL/go-zond@v0.1.1/cmd/devp2p/nodesetcmd.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 "errors" 21 "fmt" 22 "net" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 "github.com/theQRL/go-zond/core" 29 "github.com/theQRL/go-zond/core/forkid" 30 "github.com/theQRL/go-zond/p2p/enr" 31 "github.com/theQRL/go-zond/params" 32 "github.com/theQRL/go-zond/rlp" 33 "github.com/urfave/cli/v2" 34 ) 35 36 var ( 37 nodesetCommand = &cli.Command{ 38 Name: "nodeset", 39 Usage: "Node set tools", 40 Subcommands: []*cli.Command{ 41 nodesetInfoCommand, 42 nodesetFilterCommand, 43 }, 44 } 45 nodesetInfoCommand = &cli.Command{ 46 Name: "info", 47 Usage: "Shows statistics about a node set", 48 Action: nodesetInfo, 49 ArgsUsage: "<nodes.json>", 50 } 51 nodesetFilterCommand = &cli.Command{ 52 Name: "filter", 53 Usage: "Filters a node set", 54 Action: nodesetFilter, 55 ArgsUsage: "<nodes.json> filters..", 56 57 SkipFlagParsing: true, 58 } 59 ) 60 61 func nodesetInfo(ctx *cli.Context) error { 62 if ctx.NArg() < 1 { 63 return errors.New("need nodes file as argument") 64 } 65 66 ns := loadNodesJSON(ctx.Args().First()) 67 fmt.Printf("Set contains %d nodes.\n", len(ns)) 68 showAttributeCounts(ns) 69 return nil 70 } 71 72 // showAttributeCounts prints the distribution of ENR attributes in a node set. 73 func showAttributeCounts(ns nodeSet) { 74 attrcount := make(map[string]int) 75 var attrlist []interface{} 76 for _, n := range ns { 77 r := n.N.Record() 78 attrlist = r.AppendElements(attrlist[:0])[1:] 79 for i := 0; i < len(attrlist); i += 2 { 80 key := attrlist[i].(string) 81 attrcount[key]++ 82 } 83 } 84 85 var keys []string 86 var maxlength int 87 for key := range attrcount { 88 keys = append(keys, key) 89 if len(key) > maxlength { 90 maxlength = len(key) 91 } 92 } 93 sort.Strings(keys) 94 fmt.Println("ENR attribute counts:") 95 for _, key := range keys { 96 fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key]) 97 } 98 } 99 100 func nodesetFilter(ctx *cli.Context) error { 101 if ctx.NArg() < 1 { 102 return errors.New("need nodes file as argument") 103 } 104 // Parse -limit. 105 limit, err := parseFilterLimit(ctx.Args().Tail()) 106 if err != nil { 107 return err 108 } 109 // Parse the filters. 110 filter, err := andFilter(ctx.Args().Tail()) 111 if err != nil { 112 return err 113 } 114 115 // Load nodes and apply filters. 116 ns := loadNodesJSON(ctx.Args().First()) 117 result := make(nodeSet) 118 for id, n := range ns { 119 if filter(n) { 120 result[id] = n 121 } 122 } 123 if limit >= 0 { 124 result = result.topN(limit) 125 } 126 writeNodesJSON("-", result) 127 return nil 128 } 129 130 type nodeFilter func(nodeJSON) bool 131 132 type nodeFilterC struct { 133 narg int 134 fn func([]string) (nodeFilter, error) 135 } 136 137 var filterFlags = map[string]nodeFilterC{ 138 "-limit": {1, trueFilter}, // needed to skip over -limit 139 "-ip": {1, ipFilter}, 140 "-min-age": {1, minAgeFilter}, 141 "-zond-network": {1, ethFilter}, 142 "-les-server": {0, lesFilter}, 143 "-snap": {0, snapFilter}, 144 } 145 146 // parseFilters parses nodeFilters from args. 147 func parseFilters(args []string) ([]nodeFilter, error) { 148 var filters []nodeFilter 149 for len(args) > 0 { 150 fc, ok := filterFlags[args[0]] 151 if !ok { 152 return nil, fmt.Errorf("invalid filter %q", args[0]) 153 } 154 if len(args)-1 < fc.narg { 155 return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)-1) 156 } 157 filter, err := fc.fn(args[1 : 1+fc.narg]) 158 if err != nil { 159 return nil, fmt.Errorf("%s: %v", args[0], err) 160 } 161 filters = append(filters, filter) 162 args = args[1+fc.narg:] 163 } 164 return filters, nil 165 } 166 167 // parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit. 168 func parseFilterLimit(args []string) (int, error) { 169 limit := -1 170 for i, arg := range args { 171 if arg == "-limit" { 172 if i == len(args)-1 { 173 return -1, errors.New("-limit requires an argument") 174 } 175 n, err := strconv.Atoi(args[i+1]) 176 if err != nil { 177 return -1, fmt.Errorf("invalid -limit %q", args[i+1]) 178 } 179 limit = n 180 } 181 } 182 return limit, nil 183 } 184 185 // andFilter parses node filters in args and returns a single filter that requires all 186 // of them to match. 187 func andFilter(args []string) (nodeFilter, error) { 188 checks, err := parseFilters(args) 189 if err != nil { 190 return nil, err 191 } 192 f := func(n nodeJSON) bool { 193 for _, filter := range checks { 194 if !filter(n) { 195 return false 196 } 197 } 198 return true 199 } 200 return f, nil 201 } 202 203 func trueFilter(args []string) (nodeFilter, error) { 204 return func(n nodeJSON) bool { return true }, nil 205 } 206 207 func ipFilter(args []string) (nodeFilter, error) { 208 _, cidr, err := net.ParseCIDR(args[0]) 209 if err != nil { 210 return nil, err 211 } 212 f := func(n nodeJSON) bool { return cidr.Contains(n.N.IP()) } 213 return f, nil 214 } 215 216 func minAgeFilter(args []string) (nodeFilter, error) { 217 minage, err := time.ParseDuration(args[0]) 218 if err != nil { 219 return nil, err 220 } 221 f := func(n nodeJSON) bool { 222 age := n.LastResponse.Sub(n.FirstResponse) 223 return age >= minage 224 } 225 return f, nil 226 } 227 228 func ethFilter(args []string) (nodeFilter, error) { 229 var filter forkid.Filter 230 switch args[0] { 231 case "mainnet": 232 filter = forkid.NewStaticFilter(params.MainnetChainConfig, core.DefaultGenesisBlock().ToBlock()) 233 case "goerli": 234 filter = forkid.NewStaticFilter(params.GoerliChainConfig, core.DefaultGoerliGenesisBlock().ToBlock()) 235 case "sepolia": 236 filter = forkid.NewStaticFilter(params.SepoliaChainConfig, core.DefaultSepoliaGenesisBlock().ToBlock()) 237 case "holesky": 238 filter = forkid.NewStaticFilter(params.HoleskyChainConfig, core.DefaultHoleskyGenesisBlock().ToBlock()) 239 default: 240 return nil, fmt.Errorf("unknown network %q", args[0]) 241 } 242 243 f := func(n nodeJSON) bool { 244 var eth struct { 245 ForkID forkid.ID 246 Tail []rlp.RawValue `rlp:"tail"` 247 } 248 if n.N.Load(enr.WithEntry("zond", ð)) != nil { 249 return false 250 } 251 return filter(eth.ForkID) == nil 252 } 253 return f, nil 254 } 255 256 func lesFilter(args []string) (nodeFilter, error) { 257 f := func(n nodeJSON) bool { 258 var les struct { 259 Tail []rlp.RawValue `rlp:"tail"` 260 } 261 return n.N.Load(enr.WithEntry("les", &les)) == nil 262 } 263 return f, nil 264 } 265 266 func snapFilter(args []string) (nodeFilter, error) { 267 f := func(n nodeJSON) bool { 268 var snap struct { 269 Tail []rlp.RawValue `rlp:"tail"` 270 } 271 return n.N.Load(enr.WithEntry("snap", &snap)) == nil 272 } 273 return f, nil 274 }