424656519a
The new -limit option makes the filter operate on top N nodes by score. This also adds ENR attribute stats in the nodeset info command. Node set commands are now documented in README.
129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
// Copyright 2019 The go-ethereum Authors
|
|
// This file is part of go-ethereum.
|
|
//
|
|
// go-ethereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// go-ethereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
)
|
|
|
|
const jsonIndent = " "
|
|
|
|
// nodeSet is the nodes.json file format. It holds a set of node records
|
|
// as a JSON object.
|
|
type nodeSet map[enode.ID]nodeJSON
|
|
|
|
type nodeJSON struct {
|
|
Seq uint64 `json:"seq"`
|
|
N *enode.Node `json:"record"`
|
|
|
|
// The score tracks how many liveness checks were performed. It is incremented by one
|
|
// every time the node passes a check, and halved every time it doesn't.
|
|
Score int `json:"score,omitempty"`
|
|
// These two track the time of last successful contact.
|
|
FirstResponse time.Time `json:"firstResponse,omitempty"`
|
|
LastResponse time.Time `json:"lastResponse,omitempty"`
|
|
// This one tracks the time of our last attempt to contact the node.
|
|
LastCheck time.Time `json:"lastCheck,omitempty"`
|
|
}
|
|
|
|
func loadNodesJSON(file string) nodeSet {
|
|
var nodes nodeSet
|
|
if err := common.LoadJSON(file, &nodes); err != nil {
|
|
exit(err)
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
func writeNodesJSON(file string, nodes nodeSet) {
|
|
nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent)
|
|
if err != nil {
|
|
exit(err)
|
|
}
|
|
if file == "-" {
|
|
os.Stdout.Write(nodesJSON)
|
|
return
|
|
}
|
|
if err := ioutil.WriteFile(file, nodesJSON, 0644); err != nil {
|
|
exit(err)
|
|
}
|
|
}
|
|
|
|
// nodes returns the node records contained in the set.
|
|
func (ns nodeSet) nodes() []*enode.Node {
|
|
result := make([]*enode.Node, 0, len(ns))
|
|
for _, n := range ns {
|
|
result = append(result, n.N)
|
|
}
|
|
// Sort by ID.
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return bytes.Compare(result[i].ID().Bytes(), result[j].ID().Bytes()) < 0
|
|
})
|
|
return result
|
|
}
|
|
|
|
// add ensures the given nodes are present in the set.
|
|
func (ns nodeSet) add(nodes ...*enode.Node) {
|
|
for _, n := range nodes {
|
|
v := ns[n.ID()]
|
|
v.N = n
|
|
v.Seq = n.Seq()
|
|
ns[n.ID()] = v
|
|
}
|
|
}
|
|
|
|
// topN returns the top n nodes by score as a new set.
|
|
func (ns nodeSet) topN(n int) nodeSet {
|
|
if n >= len(ns) {
|
|
return ns
|
|
}
|
|
|
|
byscore := make([]nodeJSON, 0, len(ns))
|
|
for _, v := range ns {
|
|
byscore = append(byscore, v)
|
|
}
|
|
sort.Slice(byscore, func(i, j int) bool {
|
|
return byscore[i].Score >= byscore[j].Score
|
|
})
|
|
result := make(nodeSet, n)
|
|
for _, v := range byscore[:n] {
|
|
result[v.N.ID()] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// verify performs integrity checks on the node set.
|
|
func (ns nodeSet) verify() error {
|
|
for id, n := range ns {
|
|
if n.N.ID() != id {
|
|
return fmt.Errorf("invalid node %v: ID does not match ID %v in record", id, n.N.ID())
|
|
}
|
|
if n.N.Seq() != n.Seq {
|
|
return fmt.Errorf("invalid node %v: 'seq' does not match seq %d from record", id, n.N.Seq())
|
|
}
|
|
}
|
|
return nil
|
|
}
|