113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"net"
|
||
|
"os"
|
||
|
"time"
|
||
|
|
||
|
"github.com/ethereum/go-ethereum/crypto"
|
||
|
"github.com/ethereum/go-ethereum/log"
|
||
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||
|
)
|
||
|
|
||
|
type bootnodes []*discv5.Node
|
||
|
|
||
|
func (f *bootnodes) String() string {
|
||
|
return "discv5 nodes"
|
||
|
}
|
||
|
|
||
|
// Set unmarshals enode into discv5.Node.
|
||
|
func (f *bootnodes) Set(value string) error {
|
||
|
n, err := discv5.ParseNode(value)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
*f = append(*f, n)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func runBootnode(listenAddr string, nursery bootnodes) *discv5.Network {
|
||
|
key, err := crypto.GenerateKey()
|
||
|
if err != nil {
|
||
|
log.Crit("Failed to generate ecdsa key from", "error", err)
|
||
|
}
|
||
|
addr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||
|
if err != nil {
|
||
|
log.Crit("Unable to resolve UDP", "address", listenAddr, "error", err)
|
||
|
}
|
||
|
conn, err := net.ListenUDP("udp", addr)
|
||
|
if err != nil {
|
||
|
log.Crit("Unable to listen on udp", "address", addr, "error", err)
|
||
|
}
|
||
|
|
||
|
realaddr := conn.LocalAddr().(*net.UDPAddr)
|
||
|
tab, err := discv5.ListenUDP(key, conn, realaddr, "", nil)
|
||
|
if err != nil {
|
||
|
log.Crit("Failed to create discovery v5 table:", "error", err)
|
||
|
}
|
||
|
if err := tab.SetFallbackNodes(nursery); err != nil {
|
||
|
log.Crit("Failed to set fallback", "nodes", nursery, "error", err)
|
||
|
}
|
||
|
return tab
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
var (
|
||
|
listenAddr = flag.String("addr", ":0", "listen address")
|
||
|
verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-9)")
|
||
|
vmodule = flag.String("vmodule", "", "log verbosity pattern")
|
||
|
nursery = bootnodes{}
|
||
|
topic = flag.String("topic", "whisper", "topic to search for")
|
||
|
limit = flag.Int("limit", 5, "search no more than for 5 peers")
|
||
|
timer = flag.Duration("timer", 2*time.Minute, "controls how often healthcheck runs")
|
||
|
period = flag.Duration("period", 3*time.Second, "controls how often topic is searched")
|
||
|
statsPort = flag.String("stats", ":8080", "listen addr for stats")
|
||
|
)
|
||
|
flag.Var(&nursery, "n", "These nodes are used to connect to the network if the table is empty and there are no known nodes in the database.")
|
||
|
flag.Parse()
|
||
|
|
||
|
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
||
|
glogger.Verbosity(log.Lvl(*verbosity))
|
||
|
if err := glogger.Vmodule(*vmodule); err != nil {
|
||
|
log.Crit("Failed to set glog verbosity", "value", *vmodule, "err", err)
|
||
|
}
|
||
|
log.Root().SetHandler(glogger)
|
||
|
stats := NewStats(*statsPort)
|
||
|
|
||
|
healthcheck := func() {
|
||
|
tab := runBootnode(*listenAddr, nursery)
|
||
|
defer tab.Close()
|
||
|
setPeriod := make(chan time.Duration, 1)
|
||
|
setPeriod <- *period
|
||
|
found := make(chan *discv5.Node, *limit)
|
||
|
lookup := make(chan bool, 10)
|
||
|
log.Info("Started search.", "topic", *topic, "limit", *limit)
|
||
|
go tab.SearchTopic(discv5.Topic(*topic), setPeriod, found, lookup)
|
||
|
current := 0
|
||
|
failTimer := time.After(2 * time.Minute)
|
||
|
testStart := time.Now()
|
||
|
stats.Started()
|
||
|
for {
|
||
|
select {
|
||
|
case <-failTimer:
|
||
|
stats.Failed()
|
||
|
return
|
||
|
case <-lookup:
|
||
|
case n := <-found:
|
||
|
current++
|
||
|
latency := time.Since(testStart)
|
||
|
log.Info("Discovered node", "total", current, "node", n, "latency", latency)
|
||
|
stats.Discovered(current, latency)
|
||
|
if current == *limit {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for {
|
||
|
healthcheck()
|
||
|
time.Sleep(*timer)
|
||
|
}
|
||
|
}
|