github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/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/ethereum/go-ethereum/common/mclock"
    30  	"github.com/ethereum/go-ethereum/crypto"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/p2p/enode"
    33  	"github.com/ethereum/go-ethereum/p2p/enr"
    34  	lru "github.com/hashicorp/golang-lru"
    35  	"golang.org/x/sync/singleflight"
    36  	"golang.org/x/time/rate"
    37  )
    38  
    39  // Client discovers nodes by querying DNS servers.
    40  type Client struct {
    41  	cfg          Config
    42  	clock        mclock.Clock
    43  	entries      *lru.Cache
    44  	ratelimit    *rate.Limiter
    45  	singleflight singleflight.Group
    46  }
    47  
    48  // Config holds configuration options for the client.
    49  type Config struct {
    50  	Timeout         time.Duration      // timeout used for DNS lookups (default 5s)
    51  	RecheckInterval time.Duration      // time between tree root update checks (default 30min)
    52  	CacheLimit      int                // maximum number of cached records (default 1000)
    53  	RateLimit       float64            // maximum DNS requests / second (default 3)
    54  	ValidSchemes    enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
    55  	Resolver        Resolver           // the DNS resolver to use (defaults to system DNS)
    56  	Logger          log.Logger         // destination of client log messages (defaults to root logger)
    57  }
    58  
    59  // Resolver is a DNS resolver that can query TXT records.
    60  type Resolver interface {
    61  	LookupTXT(ctx context.Context, domain string) ([]string, error)
    62  }
    63  
    64  func (cfg Config) withDefaults() Config {
    65  	const (
    66  		defaultTimeout   = 5 * time.Second
    67  		defaultRecheck   = 30 * time.Minute
    68  		defaultRateLimit = 3
    69  		defaultCache     = 1000
    70  	)
    71  	if cfg.Timeout == 0 {
    72  		cfg.Timeout = defaultTimeout
    73  	}
    74  	if cfg.RecheckInterval == 0 {
    75  		cfg.RecheckInterval = defaultRecheck
    76  	}
    77  	if cfg.CacheLimit == 0 {
    78  		cfg.CacheLimit = defaultCache
    79  	}
    80  	if cfg.RateLimit == 0 {
    81  		cfg.RateLimit = defaultRateLimit
    82  	}
    83  	if cfg.ValidSchemes == nil {
    84  		cfg.ValidSchemes = enode.ValidSchemes
    85  	}
    86  	if cfg.Resolver == nil {
    87  		cfg.Resolver = new(net.Resolver)
    88  	}
    89  	if cfg.Logger == nil {
    90  		cfg.Logger = log.Root()
    91  	}
    92  	return cfg
    93  }
    94  
    95  // NewClient creates a client.
    96  func NewClient(cfg Config) *Client {
    97  	cfg = cfg.withDefaults()
    98  	cache, err := lru.New(cfg.CacheLimit)
    99  	if err != nil {
   100  		panic(err)
   101  	}
   102  	rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
   103  	return &Client{
   104  		cfg:       cfg,
   105  		entries:   cache,
   106  		clock:     mclock.System{},
   107  		ratelimit: rlimit,
   108  	}
   109  }
   110  
   111  // SyncTree downloads the entire node tree at the given URL.
   112  func (c *Client) SyncTree(url string) (*Tree, error) {
   113  	le, err := parseLink(url)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("invalid enrtree URL: %v", err)
   116  	}
   117  	ct := newClientTree(c, new(linkCache), le)
   118  	t := &Tree{entries: make(map[string]entry)}
   119  	if err := ct.syncAll(t.entries); err != nil {
   120  		return nil, err
   121  	}
   122  	t.root = ct.root
   123  	return t, nil
   124  }
   125  
   126  // NewIterator creates an iterator that visits all nodes at the
   127  // given tree URLs.
   128  func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
   129  	it := c.newRandomIterator()
   130  	for _, url := range urls {
   131  		if err := it.addTree(url); err != nil {
   132  			return nil, err
   133  		}
   134  	}
   135  	return it, nil
   136  }
   137  
   138  // resolveRoot retrieves a root entry via DNS.
   139  func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
   140  	e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) {
   141  		txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
   142  		c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
   143  		if err != nil {
   144  			return rootEntry{}, err
   145  		}
   146  		for _, txt := range txts {
   147  			if strings.HasPrefix(txt, rootPrefix) {
   148  				return parseAndVerifyRoot(txt, loc)
   149  			}
   150  		}
   151  		return rootEntry{}, nameError{loc.domain, errNoRoot}
   152  	})
   153  	return e.(rootEntry), err
   154  }
   155  
   156  func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
   157  	e, err := parseRoot(txt)
   158  	if err != nil {
   159  		return e, err
   160  	}
   161  	if !e.verifySignature(loc.pubkey) {
   162  		return e, entryError{typ: "root", err: errInvalidSig}
   163  	}
   164  	return e, nil
   165  }
   166  
   167  // resolveEntry retrieves an entry from the cache or fetches it from the network
   168  // if it isn't cached.
   169  func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   170  	// The rate limit always applies, even when the result might be cached. This is
   171  	// important because it avoids hot-spinning in consumers of node iterators created on
   172  	// this client.
   173  	if err := c.ratelimit.Wait(ctx); err != nil {
   174  		return nil, err
   175  	}
   176  	cacheKey := truncateHash(hash)
   177  	if e, ok := c.entries.Get(cacheKey); ok {
   178  		return e.(entry), nil
   179  	}
   180  
   181  	ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
   182  		e, err := c.doResolveEntry(ctx, domain, hash)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		c.entries.Add(cacheKey, e)
   187  		return e, nil
   188  	})
   189  	e, _ := ei.(entry)
   190  	return e, err
   191  }
   192  
   193  // doResolveEntry fetches an entry via DNS.
   194  func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   195  	wantHash, err := b32format.DecodeString(hash)
   196  	if err != nil {
   197  		return nil, fmt.Errorf("invalid base32 hash")
   198  	}
   199  	name := hash + "." + domain
   200  	txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
   201  	c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	for _, txt := range txts {
   206  		e, err := parseEntry(txt, c.cfg.ValidSchemes)
   207  		if err == errUnknownEntry {
   208  			continue
   209  		}
   210  		if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
   211  			err = nameError{name, errHashMismatch}
   212  		} else if err != nil {
   213  			err = nameError{name, err}
   214  		}
   215  		return e, err
   216  	}
   217  	return nil, nameError{name, errNoEntry}
   218  }
   219  
   220  // randomIterator traverses a set of trees and returns nodes found in them.
   221  type randomIterator struct {
   222  	cur      *enode.Node
   223  	ctx      context.Context
   224  	cancelFn context.CancelFunc
   225  	c        *Client
   226  
   227  	mu    sync.Mutex
   228  	lc    linkCache              // tracks tree dependencies
   229  	trees map[string]*clientTree // all trees
   230  	// buffers for syncableTrees
   231  	syncableList []*clientTree
   232  	disabledList []*clientTree
   233  }
   234  
   235  func (c *Client) newRandomIterator() *randomIterator {
   236  	ctx, cancel := context.WithCancel(context.Background())
   237  	return &randomIterator{
   238  		c:        c,
   239  		ctx:      ctx,
   240  		cancelFn: cancel,
   241  		trees:    make(map[string]*clientTree),
   242  	}
   243  }
   244  
   245  // Node returns the current node.
   246  func (it *randomIterator) Node() *enode.Node {
   247  	return it.cur
   248  }
   249  
   250  // Close closes the iterator.
   251  func (it *randomIterator) Close() {
   252  	it.cancelFn()
   253  
   254  	it.mu.Lock()
   255  	defer it.mu.Unlock()
   256  	it.trees = nil
   257  }
   258  
   259  // Next moves the iterator to the next node.
   260  func (it *randomIterator) Next() bool {
   261  	it.cur = it.nextNode()
   262  	return it.cur != nil
   263  }
   264  
   265  // addTree adds an enrtree:// URL to the iterator.
   266  func (it *randomIterator) addTree(url string) error {
   267  	le, err := parseLink(url)
   268  	if err != nil {
   269  		return fmt.Errorf("invalid enrtree URL: %v", err)
   270  	}
   271  	it.lc.addLink("", le.str)
   272  	return nil
   273  }
   274  
   275  // nextNode syncs random tree entries until it finds a node.
   276  func (it *randomIterator) nextNode() *enode.Node {
   277  	for {
   278  		ct := it.pickTree()
   279  		if ct == nil {
   280  			return nil
   281  		}
   282  		n, err := ct.syncRandom(it.ctx)
   283  		if err != nil {
   284  			if err == it.ctx.Err() {
   285  				return nil // context canceled.
   286  			}
   287  			it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
   288  			continue
   289  		}
   290  		if n != nil {
   291  			return n
   292  		}
   293  	}
   294  }
   295  
   296  // pickTree returns a random tree to sync from.
   297  func (it *randomIterator) pickTree() *clientTree {
   298  	it.mu.Lock()
   299  	defer it.mu.Unlock()
   300  
   301  	// First check if iterator was closed.
   302  	// Need to do this here to avoid nil map access in rebuildTrees.
   303  	if it.trees == nil {
   304  		return nil
   305  	}
   306  
   307  	// Rebuild the trees map if any links have changed.
   308  	if it.lc.changed {
   309  		it.rebuildTrees()
   310  		it.lc.changed = false
   311  	}
   312  
   313  	for {
   314  		canSync, trees := it.syncableTrees()
   315  		switch {
   316  		case canSync:
   317  			// Pick a random tree.
   318  			return trees[rand.Intn(len(trees))]
   319  		case len(trees) > 0:
   320  			// No sync action can be performed on any tree right now. The only meaningful
   321  			// thing to do is waiting for any root record to get updated.
   322  			if !it.waitForRootUpdates(trees) {
   323  				// Iterator was closed while waiting.
   324  				return nil
   325  			}
   326  		default:
   327  			// There are no trees left, the iterator was closed.
   328  			return nil
   329  		}
   330  	}
   331  }
   332  
   333  // syncableTrees finds trees on which any meaningful sync action can be performed.
   334  func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
   335  	// Resize tree lists.
   336  	it.syncableList = it.syncableList[:0]
   337  	it.disabledList = it.disabledList[:0]
   338  
   339  	// Partition them into the two lists.
   340  	for _, ct := range it.trees {
   341  		if ct.canSyncRandom() {
   342  			it.syncableList = append(it.syncableList, ct)
   343  		} else {
   344  			it.disabledList = append(it.disabledList, ct)
   345  		}
   346  	}
   347  	if len(it.syncableList) > 0 {
   348  		return true, it.syncableList
   349  	}
   350  	return false, it.disabledList
   351  }
   352  
   353  // waitForRootUpdates waits for the closest scheduled root check time on the given trees.
   354  func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
   355  	var minTree *clientTree
   356  	var nextCheck mclock.AbsTime
   357  	for _, ct := range trees {
   358  		check := ct.nextScheduledRootCheck()
   359  		if minTree == nil || check < nextCheck {
   360  			minTree = ct
   361  			nextCheck = check
   362  		}
   363  	}
   364  
   365  	sleep := nextCheck.Sub(it.c.clock.Now())
   366  	it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
   367  	timeout := it.c.clock.NewTimer(sleep)
   368  	defer timeout.Stop()
   369  	select {
   370  	case <-timeout.C():
   371  		return true
   372  	case <-it.ctx.Done():
   373  		return false // Iterator was closed.
   374  	}
   375  }
   376  
   377  // rebuildTrees rebuilds the 'trees' map.
   378  func (it *randomIterator) rebuildTrees() {
   379  	// Delete removed trees.
   380  	for loc := range it.trees {
   381  		if !it.lc.isReferenced(loc) {
   382  			delete(it.trees, loc)
   383  		}
   384  	}
   385  	// Add new trees.
   386  	for loc := range it.lc.backrefs {
   387  		if it.trees[loc] == nil {
   388  			link, _ := parseLink(linkPrefix + loc)
   389  			it.trees[loc] = newClientTree(it.c, &it.lc, link)
   390  		}
   391  	}
   392  }