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