|
@@ -17,8 +17,12 @@
|
|
|
package main
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"net"
|
|
"net"
|
|
|
|
|
+ "sort"
|
|
|
|
|
+ "strconv"
|
|
|
|
|
+ "strings"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/core/forkid"
|
|
"github.com/ethereum/go-ethereum/core/forkid"
|
|
@@ -60,25 +64,64 @@ func nodesetInfo(ctx *cli.Context) error {
|
|
|
|
|
|
|
|
ns := loadNodesJSON(ctx.Args().First())
|
|
ns := loadNodesJSON(ctx.Args().First())
|
|
|
fmt.Printf("Set contains %d nodes.\n", len(ns))
|
|
fmt.Printf("Set contains %d nodes.\n", len(ns))
|
|
|
|
|
+ showAttributeCounts(ns)
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// showAttributeCounts prints the distribution of ENR attributes in a node set.
|
|
|
|
|
+func showAttributeCounts(ns nodeSet) {
|
|
|
|
|
+ attrcount := make(map[string]int)
|
|
|
|
|
+ var attrlist []interface{}
|
|
|
|
|
+ for _, n := range ns {
|
|
|
|
|
+ r := n.N.Record()
|
|
|
|
|
+ attrlist = r.AppendElements(attrlist[:0])[1:]
|
|
|
|
|
+ for i := 0; i < len(attrlist); i += 2 {
|
|
|
|
|
+ key := attrlist[i].(string)
|
|
|
|
|
+ attrcount[key]++
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var keys []string
|
|
|
|
|
+ var maxlength int
|
|
|
|
|
+ for key := range attrcount {
|
|
|
|
|
+ keys = append(keys, key)
|
|
|
|
|
+ if len(key) > maxlength {
|
|
|
|
|
+ maxlength = len(key)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ sort.Strings(keys)
|
|
|
|
|
+ fmt.Println("ENR attribute counts:")
|
|
|
|
|
+ for _, key := range keys {
|
|
|
|
|
+ fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key])
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func nodesetFilter(ctx *cli.Context) error {
|
|
func nodesetFilter(ctx *cli.Context) error {
|
|
|
if ctx.NArg() < 1 {
|
|
if ctx.NArg() < 1 {
|
|
|
return fmt.Errorf("need nodes file as argument")
|
|
return fmt.Errorf("need nodes file as argument")
|
|
|
}
|
|
}
|
|
|
- ns := loadNodesJSON(ctx.Args().First())
|
|
|
|
|
|
|
+ // Parse -limit.
|
|
|
|
|
+ limit, err := parseFilterLimit(ctx.Args().Tail())
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ // Parse the filters.
|
|
|
filter, err := andFilter(ctx.Args().Tail())
|
|
filter, err := andFilter(ctx.Args().Tail())
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Load nodes and apply filters.
|
|
|
|
|
+ ns := loadNodesJSON(ctx.Args().First())
|
|
|
result := make(nodeSet)
|
|
result := make(nodeSet)
|
|
|
for id, n := range ns {
|
|
for id, n := range ns {
|
|
|
if filter(n) {
|
|
if filter(n) {
|
|
|
result[id] = n
|
|
result[id] = n
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ if limit >= 0 {
|
|
|
|
|
+ result = result.topN(limit)
|
|
|
|
|
+ }
|
|
|
writeNodesJSON("-", result)
|
|
writeNodesJSON("-", result)
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
@@ -91,6 +134,7 @@ type nodeFilterC struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var filterFlags = map[string]nodeFilterC{
|
|
var filterFlags = map[string]nodeFilterC{
|
|
|
|
|
+ "-limit": {1, trueFilter}, // needed to skip over -limit
|
|
|
"-ip": {1, ipFilter},
|
|
"-ip": {1, ipFilter},
|
|
|
"-min-age": {1, minAgeFilter},
|
|
"-min-age": {1, minAgeFilter},
|
|
|
"-eth-network": {1, ethFilter},
|
|
"-eth-network": {1, ethFilter},
|
|
@@ -98,6 +142,7 @@ var filterFlags = map[string]nodeFilterC{
|
|
|
"-snap": {0, snapFilter},
|
|
"-snap": {0, snapFilter},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// parseFilters parses nodeFilters from args.
|
|
|
func parseFilters(args []string) ([]nodeFilter, error) {
|
|
func parseFilters(args []string) ([]nodeFilter, error) {
|
|
|
var filters []nodeFilter
|
|
var filters []nodeFilter
|
|
|
for len(args) > 0 {
|
|
for len(args) > 0 {
|
|
@@ -118,6 +163,26 @@ func parseFilters(args []string) ([]nodeFilter, error) {
|
|
|
return filters, nil
|
|
return filters, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit.
|
|
|
|
|
+func parseFilterLimit(args []string) (int, error) {
|
|
|
|
|
+ limit := -1
|
|
|
|
|
+ for i, arg := range args {
|
|
|
|
|
+ if arg == "-limit" {
|
|
|
|
|
+ if i == len(args)-1 {
|
|
|
|
|
+ return -1, errors.New("-limit requires an argument")
|
|
|
|
|
+ }
|
|
|
|
|
+ n, err := strconv.Atoi(args[i+1])
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return -1, fmt.Errorf("invalid -limit %q", args[i+1])
|
|
|
|
|
+ }
|
|
|
|
|
+ limit = n
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return limit, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// andFilter parses node filters in args and and returns a single filter that requires all
|
|
|
|
|
+// of them to match.
|
|
|
func andFilter(args []string) (nodeFilter, error) {
|
|
func andFilter(args []string) (nodeFilter, error) {
|
|
|
checks, err := parseFilters(args)
|
|
checks, err := parseFilters(args)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -134,6 +199,10 @@ func andFilter(args []string) (nodeFilter, error) {
|
|
|
return f, nil
|
|
return f, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func trueFilter(args []string) (nodeFilter, error) {
|
|
|
|
|
+ return func(n nodeJSON) bool { return true }, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func ipFilter(args []string) (nodeFilter, error) {
|
|
func ipFilter(args []string) (nodeFilter, error) {
|
|
|
_, cidr, err := net.ParseCIDR(args[0])
|
|
_, cidr, err := net.ParseCIDR(args[0])
|
|
|
if err != nil {
|
|
if err != nil {
|