github.com/dominant-strategies/go-quai@v0.28.2/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/dominant-strategies/go-quai/common/mclock"
    30  	"github.com/dominant-strategies/go-quai/crypto"
    31  	"github.com/dominant-strategies/go-quai/log"
    32  	"github.com/dominant-strategies/go-quai/p2p/enode"
    33  	"github.com/dominant-strategies/go-quai/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.Log
    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  	return e, nil
   162  }
   163  
   164  // resolveEntry retrieves an entry from the cache or fetches it from the network
   165  // if it isn't cached.
   166  func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   167  	// The rate limit always applies, even when the result might be cached. This is
   168  	// important because it avoids hot-spinning in consumers of node iterators created on
   169  	// this client.
   170  	if err := c.ratelimit.Wait(ctx); err != nil {
   171  		return nil, err
   172  	}
   173  	cacheKey := truncateHash(hash)
   174  	if e, ok := c.entries.Get(cacheKey); ok {
   175  		return e.(entry), nil
   176  	}
   177  
   178  	ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
   179  		e, err := c.doResolveEntry(ctx, domain, hash)
   180  		if err != nil {
   181  			return nil, err
   182  		}
   183  		c.entries.Add(cacheKey, e)
   184  		return e, nil
   185  	})
   186  	e, _ := ei.(entry)
   187  	return e, err
   188  }
   189  
   190  // doResolveEntry fetches an entry via DNS.
   191  func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   192  	wantHash, err := b32format.DecodeString(hash)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("invalid base32 hash")
   195  	}
   196  	name := hash + "." + domain
   197  	txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
   198  	c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	for _, txt := range txts {
   203  		e, err := parseEntry(txt, c.cfg.ValidSchemes)
   204  		if err == errUnknownEntry {
   205  			continue
   206  		}
   207  		if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
   208  			err = nameError{name, errHashMismatch}
   209  		} else if err != nil {
   210  			err = nameError{name, err}
   211  		}
   212  		return e, err
   213  	}
   214  	return nil, nameError{name, errNoEntry}
   215  }
   216  
   217  // randomIterator traverses a set of trees and returns nodes found in them.
   218  type randomIterator struct {
   219  	cur      *enode.Node
   220  	ctx      context.Context
   221  	cancelFn context.CancelFunc
   222  	c        *Client
   223  
   224  	mu    sync.Mutex
   225  	lc    linkCache              // tracks tree dependencies
   226  	trees map[string]*clientTree // all trees
   227  	// buffers for syncableTrees
   228  	syncableList []*clientTree
   229  	disabledList []*clientTree
   230  }
   231  
   232  func (c *Client) newRandomIterator() *randomIterator {
   233  	ctx, cancel := context.WithCancel(context.Background())
   234  	return &randomIterator{
   235  		c:        c,
   236  		ctx:      ctx,
   237  		cancelFn: cancel,
   238  		trees:    make(map[string]*clientTree),
   239  	}
   240  }
   241  
   242  // Node returns the current node.
   243  func (it *randomIterator) Node() *enode.Node {
   244  	return it.cur
   245  }
   246  
   247  // Close closes the iterator.
   248  func (it *randomIterator) Close() {
   249  	it.cancelFn()
   250  
   251  	it.mu.Lock()
   252  	defer it.mu.Unlock()
   253  	it.trees = nil
   254  }
   255  
   256  // Next moves the iterator to the next node.
   257  func (it *randomIterator) Next() bool {
   258  	it.cur = it.nextNode()
   259  	return it.cur != nil
   260  }
   261  
   262  // addTree adds an enrtree:// URL to the iterator.
   263  func (it *randomIterator) addTree(url string) error {
   264  	le, err := parseLink(url)
   265  	if err != nil {
   266  		return fmt.Errorf("invalid enrtree URL: %v", err)
   267  	}
   268  	it.lc.addLink("", le.str)
   269  	return nil
   270  }
   271  
   272  // nextNode syncs random tree entries until it finds a node.
   273  func (it *randomIterator) nextNode() *enode.Node {
   274  	for {
   275  		ct := it.pickTree()
   276  		if ct == nil {
   277  			return nil
   278  		}
   279  		n, err := ct.syncRandom(it.ctx)
   280  		if err != nil {
   281  			if err == it.ctx.Err() {
   282  				return nil // context canceled.
   283  			}
   284  			it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
   285  			continue
   286  		}
   287  		if n != nil {
   288  			return n
   289  		}
   290  	}
   291  }
   292  
   293  // pickTree returns a random tree to sync from.
   294  func (it *randomIterator) pickTree() *clientTree {
   295  	it.mu.Lock()
   296  	defer it.mu.Unlock()
   297  
   298  	// First check if iterator was closed.
   299  	// Need to do this here to avoid nil map access in rebuildTrees.
   300  	if it.trees == nil {
   301  		return nil
   302  	}
   303  
   304  	// Rebuild the trees map if any links have changed.
   305  	if it.lc.changed {
   306  		it.rebuildTrees()
   307  		it.lc.changed = false
   308  	}
   309  
   310  	for {
   311  		canSync, trees := it.syncableTrees()
   312  		switch {
   313  		case canSync:
   314  			// Pick a random tree.
   315  			return trees[rand.Intn(len(trees))]
   316  		case len(trees) > 0:
   317  			// No sync action can be performed on any tree right now. The only meaningful
   318  			// thing to do is waiting for any root record to get updated.
   319  			if !it.waitForRootUpdates(trees) {
   320  				// Iterator was closed while waiting.
   321  				return nil
   322  			}
   323  		default:
   324  			// There are no trees left, the iterator was closed.
   325  			return nil
   326  		}
   327  	}
   328  }
   329  
   330  // syncableTrees finds trees on which any meaningful sync action can be performed.
   331  func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
   332  	// Resize tree lists.
   333  	it.syncableList = it.syncableList[:0]
   334  	it.disabledList = it.disabledList[:0]
   335  
   336  	// Partition them into the two lists.
   337  	for _, ct := range it.trees {
   338  		if ct.canSyncRandom() {
   339  			it.syncableList = append(it.syncableList, ct)
   340  		} else {
   341  			it.disabledList = append(it.disabledList, ct)
   342  		}
   343  	}
   344  	if len(it.syncableList) > 0 {
   345  		return true, it.syncableList
   346  	}
   347  	return false, it.disabledList
   348  }
   349  
   350  // waitForRootUpdates waits for the closest scheduled root check time on the given trees.
   351  func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
   352  	var minTree *clientTree
   353  	var nextCheck mclock.AbsTime
   354  	for _, ct := range trees {
   355  		check := ct.nextScheduledRootCheck()
   356  		if minTree == nil || check < nextCheck {
   357  			minTree = ct
   358  			nextCheck = check
   359  		}
   360  	}
   361  
   362  	sleep := nextCheck.Sub(it.c.clock.Now())
   363  	it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
   364  	timeout := it.c.clock.NewTimer(sleep)
   365  	defer timeout.Stop()
   366  	select {
   367  	case <-timeout.C():
   368  		return true
   369  	case <-it.ctx.Done():
   370  		return false // Iterator was closed.
   371  	}
   372  }
   373  
   374  // rebuildTrees rebuilds the 'trees' map.
   375  func (it *randomIterator) rebuildTrees() {
   376  	// Delete removed trees.
   377  	for loc := range it.trees {
   378  		if !it.lc.isReferenced(loc) {
   379  			delete(it.trees, loc)
   380  		}
   381  	}
   382  	// Add new trees.
   383  	for loc := range it.lc.backrefs {
   384  		if it.trees[loc] == nil {
   385  			link, _ := parseLink(linkPrefix + loc)
   386  			it.trees[loc] = newClientTree(it.c, &it.lc, link)
   387  		}
   388  	}
   389  }