github.com/theQRL/go-zond@v0.2.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  	"-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 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, core.DefaultGenesisBlock().ToBlock())
   232  	default:
   233  		return nil, fmt.Errorf("unknown network %q", args[0])
   234  	}
   235  
   236  	f := func(n nodeJSON) bool {
   237  		var eth struct {
   238  			ForkID forkid.ID
   239  			Tail   []rlp.RawValue `rlp:"tail"`
   240  		}
   241  		if n.N.Load(enr.WithEntry("zond", &eth)) != nil {
   242  			return false
   243  		}
   244  		return filter(eth.ForkID) == nil
   245  	}
   246  	return f, nil
   247  }
   248  
   249  func snapFilter(args []string) (nodeFilter, error) {
   250  	f := func(n nodeJSON) bool {
   251  		var snap struct {
   252  			Tail []rlp.RawValue `rlp:"tail"`
   253  		}
   254  		return n.N.Load(enr.WithEntry("snap", &snap)) == nil
   255  	}
   256  	return f, nil
   257  }