github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/p2p/dnsdisc/client.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser 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  // The go-ethereum library 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 Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package dnsdisc
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"math/rand"
    24  	"net"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/kisexp/xdchain/common/mclock"
    30  	"github.com/kisexp/xdchain/crypto"
    31  	"github.com/kisexp/xdchain/log"
    32  	"github.com/kisexp/xdchain/p2p/enode"
    33  	"github.com/kisexp/xdchain/p2p/enr"
    34  	lru "github.com/hashicorp/golang-lru"
    35  	"golang.org/x/time/rate"
    36  )
    37  
    38  // Client discovers nodes by querying DNS servers.
    39  type Client struct {
    40  	cfg     Config
    41  	clock   mclock.Clock
    42  	entries *lru.Cache
    43  }
    44  
    45  // Config holds configuration options for the client.
    46  type Config struct {
    47  	Timeout         time.Duration      // timeout used for DNS lookups (default 5s)
    48  	RecheckInterval time.Duration      // time between tree root update checks (default 30min)
    49  	CacheLimit      int                // maximum number of cached records (default 1000)
    50  	RateLimit       float64            // maximum DNS requests / second (default 3)
    51  	ValidSchemes    enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
    52  	Resolver        Resolver           // the DNS resolver to use (defaults to system DNS)
    53  	Logger          log.Logger         // destination of client log messages (defaults to root logger)
    54  }
    55  
    56  // Resolver is a DNS resolver that can query TXT records.
    57  type Resolver interface {
    58  	LookupTXT(ctx context.Context, domain string) ([]string, error)
    59  }
    60  
    61  func (cfg Config) withDefaults() Config {
    62  	const (
    63  		defaultTimeout   = 5 * time.Second
    64  		defaultRecheck   = 30 * time.Minute
    65  		defaultRateLimit = 3
    66  		defaultCache     = 1000
    67  	)
    68  	if cfg.Timeout == 0 {
    69  		cfg.Timeout = defaultTimeout
    70  	}
    71  	if cfg.RecheckInterval == 0 {
    72  		cfg.RecheckInterval = defaultRecheck
    73  	}
    74  	if cfg.CacheLimit == 0 {
    75  		cfg.CacheLimit = defaultCache
    76  	}
    77  	if cfg.RateLimit == 0 {
    78  		cfg.RateLimit = defaultRateLimit
    79  	}
    80  	if cfg.ValidSchemes == nil {
    81  		cfg.ValidSchemes = enode.ValidSchemes
    82  	}
    83  	if cfg.Resolver == nil {
    84  		cfg.Resolver = new(net.Resolver)
    85  	}
    86  	if cfg.Logger == nil {
    87  		cfg.Logger = log.Root()
    88  	}
    89  	return cfg
    90  }
    91  
    92  // NewClient creates a client.
    93  func NewClient(cfg Config) *Client {
    94  	cfg = cfg.withDefaults()
    95  	cache, err := lru.New(cfg.CacheLimit)
    96  	if err != nil {
    97  		panic(err)
    98  	}
    99  	rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
   100  	cfg.Resolver = &rateLimitResolver{cfg.Resolver, rlimit}
   101  	return &Client{cfg: cfg, entries: cache, clock: mclock.System{}}
   102  }
   103  
   104  // SyncTree downloads the entire node tree at the given URL.
   105  func (c *Client) SyncTree(url string) (*Tree, error) {
   106  	le, err := parseLink(url)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("invalid enrtree URL: %v", err)
   109  	}
   110  	ct := newClientTree(c, new(linkCache), le)
   111  	t := &Tree{entries: make(map[string]entry)}
   112  	if err := ct.syncAll(t.entries); err != nil {
   113  		return nil, err
   114  	}
   115  	t.root = ct.root
   116  	return t, nil
   117  }
   118  
   119  // NewIterator creates an iterator that visits all nodes at the
   120  // given tree URLs.
   121  func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
   122  	it := c.newRandomIterator()
   123  	for _, url := range urls {
   124  		if err := it.addTree(url); err != nil {
   125  			return nil, err
   126  		}
   127  	}
   128  	return it, nil
   129  }
   130  
   131  // resolveRoot retrieves a root entry via DNS.
   132  func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
   133  	txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
   134  	c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
   135  	if err != nil {
   136  		return rootEntry{}, err
   137  	}
   138  	for _, txt := range txts {
   139  		if strings.HasPrefix(txt, rootPrefix) {
   140  			return parseAndVerifyRoot(txt, loc)
   141  		}
   142  	}
   143  	return rootEntry{}, nameError{loc.domain, errNoRoot}
   144  }
   145  
   146  func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
   147  	e, err := parseRoot(txt)
   148  	if err != nil {
   149  		return e, err
   150  	}
   151  	if !e.verifySignature(loc.pubkey) {
   152  		return e, entryError{typ: "root", err: errInvalidSig}
   153  	}
   154  	return e, nil
   155  }
   156  
   157  // resolveEntry retrieves an entry from the cache or fetches it from the network
   158  // if it isn't cached.
   159  func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   160  	cacheKey := truncateHash(hash)
   161  	if e, ok := c.entries.Get(cacheKey); ok {
   162  		return e.(entry), nil
   163  	}
   164  	e, err := c.doResolveEntry(ctx, domain, hash)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	c.entries.Add(cacheKey, e)
   169  	return e, nil
   170  }
   171  
   172  // doResolveEntry fetches an entry via DNS.
   173  func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   174  	wantHash, err := b32format.DecodeString(hash)
   175  	if err != nil {
   176  		return nil, fmt.Errorf("invalid base32 hash")
   177  	}
   178  	name := hash + "." + domain
   179  	txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
   180  	c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	for _, txt := range txts {
   185  		e, err := parseEntry(txt, c.cfg.ValidSchemes)
   186  		if err == errUnknownEntry {
   187  			continue
   188  		}
   189  		if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
   190  			err = nameError{name, errHashMismatch}
   191  		} else if err != nil {
   192  			err = nameError{name, err}
   193  		}
   194  		return e, err
   195  	}
   196  	return nil, nameError{name, errNoEntry}
   197  }
   198  
   199  // rateLimitResolver applies a rate limit to a Resolver.
   200  type rateLimitResolver struct {
   201  	r       Resolver
   202  	limiter *rate.Limiter
   203  }
   204  
   205  func (r *rateLimitResolver) LookupTXT(ctx context.Context, domain string) ([]string, error) {
   206  	if err := r.limiter.Wait(ctx); err != nil {
   207  		return nil, err
   208  	}
   209  	return r.r.LookupTXT(ctx, domain)
   210  }
   211  
   212  // randomIterator traverses a set of trees and returns nodes found in them.
   213  type randomIterator struct {
   214  	cur      *enode.Node
   215  	ctx      context.Context
   216  	cancelFn context.CancelFunc
   217  	c        *Client
   218  
   219  	mu    sync.Mutex
   220  	trees map[string]*clientTree // all trees
   221  	lc    linkCache              // tracks tree dependencies
   222  }
   223  
   224  func (c *Client) newRandomIterator() *randomIterator {
   225  	ctx, cancel := context.WithCancel(context.Background())
   226  	return &randomIterator{
   227  		c:        c,
   228  		ctx:      ctx,
   229  		cancelFn: cancel,
   230  		trees:    make(map[string]*clientTree),
   231  	}
   232  }
   233  
   234  // Node returns the current node.
   235  func (it *randomIterator) Node() *enode.Node {
   236  	return it.cur
   237  }
   238  
   239  // Close closes the iterator.
   240  func (it *randomIterator) Close() {
   241  	it.mu.Lock()
   242  	defer it.mu.Unlock()
   243  
   244  	it.cancelFn()
   245  	it.trees = nil
   246  }
   247  
   248  // Next moves the iterator to the next node.
   249  func (it *randomIterator) Next() bool {
   250  	it.cur = it.nextNode()
   251  	return it.cur != nil
   252  }
   253  
   254  // addTree adds an enrtree:// URL to the iterator.
   255  func (it *randomIterator) addTree(url string) error {
   256  	le, err := parseLink(url)
   257  	if err != nil {
   258  		return fmt.Errorf("invalid enrtree URL: %v", err)
   259  	}
   260  	it.lc.addLink("", le.str)
   261  	return nil
   262  }
   263  
   264  // nextNode syncs random tree entries until it finds a node.
   265  func (it *randomIterator) nextNode() *enode.Node {
   266  	for {
   267  		ct := it.nextTree()
   268  		if ct == nil {
   269  			return nil
   270  		}
   271  		n, err := ct.syncRandom(it.ctx)
   272  		if err != nil {
   273  			if err == it.ctx.Err() {
   274  				return nil // context canceled.
   275  			}
   276  			it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
   277  			continue
   278  		}
   279  		if n != nil {
   280  			return n
   281  		}
   282  	}
   283  }
   284  
   285  // nextTree returns a random tree.
   286  func (it *randomIterator) nextTree() *clientTree {
   287  	it.mu.Lock()
   288  	defer it.mu.Unlock()
   289  
   290  	if it.lc.changed {
   291  		it.rebuildTrees()
   292  		it.lc.changed = false
   293  	}
   294  	if len(it.trees) == 0 {
   295  		return nil
   296  	}
   297  	limit := rand.Intn(len(it.trees))
   298  	for _, ct := range it.trees {
   299  		if limit == 0 {
   300  			return ct
   301  		}
   302  		limit--
   303  	}
   304  	return nil
   305  }
   306  
   307  // rebuildTrees rebuilds the 'trees' map.
   308  func (it *randomIterator) rebuildTrees() {
   309  	// Delete removed trees.
   310  	for loc := range it.trees {
   311  		if !it.lc.isReferenced(loc) {
   312  			delete(it.trees, loc)
   313  		}
   314  	}
   315  	// Add new trees.
   316  	for loc := range it.lc.backrefs {
   317  		if it.trees[loc] == nil {
   318  			link, _ := parseLink(linkPrefix + loc)
   319  			it.trees[loc] = newClientTree(it.c, &it.lc, link)
   320  		}
   321  	}
   322  }