github.com/Cleverse/go-ethereum@v0.0.0-20220927095127-45113064e7f2/p2p/dnsdisc/client.go (about)

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