github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/p2p/dnsdisc/sync.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  	"context"
    21  	"math/rand"
    22  	"time"
    23  
    24  	"github.com/ethereum/go-ethereum/common/mclock"
    25  	"github.com/ethereum/go-ethereum/p2p/enode"
    26  )
    27  
    28  // This is the number of consecutive leaf requests that may fail before
    29  // we consider re-resolving the tree root.
    30  const rootRecheckFailCount = 5
    31  
    32  // clientTree is a full tree being synced.
    33  type clientTree struct {
    34  	c   *Client
    35  	loc *linkEntry // link to this tree
    36  
    37  	lastRootCheck mclock.AbsTime // last revalidation of root
    38  	leafFailCount int
    39  	rootFailCount int
    40  
    41  	root  *rootEntry
    42  	enrs  *subtreeSync
    43  	links *subtreeSync
    44  
    45  	lc         *linkCache          // tracks all links between all trees
    46  	curLinks   map[string]struct{} // links contained in this tree
    47  	linkGCRoot string              // root on which last link GC has run
    48  }
    49  
    50  func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree {
    51  	return &clientTree{c: c, lc: lc, loc: loc}
    52  }
    53  
    54  // syncAll retrieves all entries of the tree.
    55  func (ct *clientTree) syncAll(dest map[string]entry) error {
    56  	if err := ct.updateRoot(context.Background()); err != nil {
    57  		return err
    58  	}
    59  	if err := ct.links.resolveAll(dest); err != nil {
    60  		return err
    61  	}
    62  	if err := ct.enrs.resolveAll(dest); err != nil {
    63  		return err
    64  	}
    65  	return nil
    66  }
    67  
    68  // syncRandom retrieves a single entry of the tree. The Node return value
    69  // is non-nil if the entry was a node.
    70  func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) {
    71  	if ct.rootUpdateDue() {
    72  		if err := ct.updateRoot(ctx); err != nil {
    73  			return nil, err
    74  		}
    75  	}
    76  
    77  	// Update fail counter for leaf request errors.
    78  	defer func() {
    79  		if err != nil {
    80  			ct.leafFailCount++
    81  		}
    82  	}()
    83  
    84  	// Link tree sync has priority, run it to completion before syncing ENRs.
    85  	if !ct.links.done() {
    86  		err := ct.syncNextLink(ctx)
    87  		return nil, err
    88  	}
    89  	ct.gcLinks()
    90  
    91  	// Sync next random entry in ENR tree. Once every node has been visited, we simply
    92  	// start over. This is fine because entries are cached internally by the client LRU
    93  	// also by DNS resolvers.
    94  	if ct.enrs.done() {
    95  		ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
    96  	}
    97  	return ct.syncNextRandomENR(ctx)
    98  }
    99  
   100  // canSyncRandom checks if any meaningful action can be performed by syncRandom.
   101  func (ct *clientTree) canSyncRandom() bool {
   102  	// Note: the check for non-zero leaf count is very important here.
   103  	// If we're done syncing all nodes, and no leaves were found, the tree
   104  	// is empty and we can't use it for sync.
   105  	return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0
   106  }
   107  
   108  // gcLinks removes outdated links from the global link cache. GC runs once
   109  // when the link sync finishes.
   110  func (ct *clientTree) gcLinks() {
   111  	if !ct.links.done() || ct.root.lroot == ct.linkGCRoot {
   112  		return
   113  	}
   114  	ct.lc.resetLinks(ct.loc.str, ct.curLinks)
   115  	ct.linkGCRoot = ct.root.lroot
   116  }
   117  
   118  func (ct *clientTree) syncNextLink(ctx context.Context) error {
   119  	hash := ct.links.missing[0]
   120  	e, err := ct.links.resolveNext(ctx, hash)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	ct.links.missing = ct.links.missing[1:]
   125  
   126  	if dest, ok := e.(*linkEntry); ok {
   127  		ct.lc.addLink(ct.loc.str, dest.str)
   128  		ct.curLinks[dest.str] = struct{}{}
   129  	}
   130  	return nil
   131  }
   132  
   133  func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
   134  	index := rand.Intn(len(ct.enrs.missing))
   135  	hash := ct.enrs.missing[index]
   136  	e, err := ct.enrs.resolveNext(ctx, hash)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	ct.enrs.missing = removeHash(ct.enrs.missing, index)
   141  	if ee, ok := e.(*enrEntry); ok {
   142  		return ee.node, nil
   143  	}
   144  	return nil, nil
   145  }
   146  
   147  func (ct *clientTree) String() string {
   148  	return ct.loc.String()
   149  }
   150  
   151  // removeHash removes the element at index from h.
   152  func removeHash(h []string, index int) []string {
   153  	if len(h) == 1 {
   154  		return nil
   155  	}
   156  	last := len(h) - 1
   157  	if index < last {
   158  		h[index] = h[last]
   159  		h[last] = ""
   160  	}
   161  	return h[:last]
   162  }
   163  
   164  // updateRoot ensures that the given tree has an up-to-date root.
   165  func (ct *clientTree) updateRoot(ctx context.Context) error {
   166  	if !ct.slowdownRootUpdate(ctx) {
   167  		return ctx.Err()
   168  	}
   169  
   170  	ct.lastRootCheck = ct.c.clock.Now()
   171  	ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout)
   172  	defer cancel()
   173  	root, err := ct.c.resolveRoot(ctx, ct.loc)
   174  	if err != nil {
   175  		ct.rootFailCount++
   176  		return err
   177  	}
   178  	ct.root = &root
   179  	ct.rootFailCount = 0
   180  	ct.leafFailCount = 0
   181  
   182  	// Invalidate subtrees if changed.
   183  	if ct.links == nil || root.lroot != ct.links.root {
   184  		ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
   185  		ct.curLinks = make(map[string]struct{})
   186  	}
   187  	if ct.enrs == nil || root.eroot != ct.enrs.root {
   188  		ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
   189  	}
   190  	return nil
   191  }
   192  
   193  // rootUpdateDue returns true when a root update is needed.
   194  func (ct *clientTree) rootUpdateDue() bool {
   195  	tooManyFailures := ct.leafFailCount > rootRecheckFailCount
   196  	scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck()
   197  	return ct.root == nil || tooManyFailures || scheduledCheck
   198  }
   199  
   200  func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime {
   201  	return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval)
   202  }
   203  
   204  // slowdownRootUpdate applies a delay to root resolution if is tried
   205  // too frequently. This avoids busy polling when the client is offline.
   206  // Returns true if the timeout passed, false if sync was canceled.
   207  func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool {
   208  	var delay time.Duration
   209  	switch {
   210  	case ct.rootFailCount > 20:
   211  		delay = 10 * time.Second
   212  	case ct.rootFailCount > 5:
   213  		delay = 5 * time.Second
   214  	default:
   215  		return true
   216  	}
   217  	timeout := ct.c.clock.NewTimer(delay)
   218  	defer timeout.Stop()
   219  	select {
   220  	case <-timeout.C():
   221  		return true
   222  	case <-ctx.Done():
   223  		return false
   224  	}
   225  }
   226  
   227  // subtreeSync is the sync of an ENR or link subtree.
   228  type subtreeSync struct {
   229  	c       *Client
   230  	loc     *linkEntry
   231  	root    string
   232  	missing []string // missing tree node hashes
   233  	link    bool     // true if this sync is for the link tree
   234  	leaves  int      // counter of synced leaves
   235  }
   236  
   237  func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
   238  	return &subtreeSync{c, loc, root, []string{root}, link, 0}
   239  }
   240  
   241  func (ts *subtreeSync) done() bool {
   242  	return len(ts.missing) == 0
   243  }
   244  
   245  func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
   246  	for !ts.done() {
   247  		hash := ts.missing[0]
   248  		ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
   249  		e, err := ts.resolveNext(ctx, hash)
   250  		cancel()
   251  		if err != nil {
   252  			return err
   253  		}
   254  		dest[hash] = e
   255  		ts.missing = ts.missing[1:]
   256  	}
   257  	return nil
   258  }
   259  
   260  func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
   261  	e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	switch e := e.(type) {
   266  	case *enrEntry:
   267  		if ts.link {
   268  			return nil, errENRInLinkTree
   269  		}
   270  		ts.leaves++
   271  	case *linkEntry:
   272  		if !ts.link {
   273  			return nil, errLinkInENRTree
   274  		}
   275  		ts.leaves++
   276  	case *branchEntry:
   277  		ts.missing = append(ts.missing, e.children...)
   278  	}
   279  	return e, nil
   280  }
   281  
   282  // linkCache tracks links between trees.
   283  type linkCache struct {
   284  	backrefs map[string]map[string]struct{}
   285  	changed  bool
   286  }
   287  
   288  func (lc *linkCache) isReferenced(r string) bool {
   289  	return len(lc.backrefs[r]) != 0
   290  }
   291  
   292  func (lc *linkCache) addLink(from, to string) {
   293  	if _, ok := lc.backrefs[to][from]; ok {
   294  		return
   295  	}
   296  
   297  	if lc.backrefs == nil {
   298  		lc.backrefs = make(map[string]map[string]struct{})
   299  	}
   300  	if _, ok := lc.backrefs[to]; !ok {
   301  		lc.backrefs[to] = make(map[string]struct{})
   302  	}
   303  	lc.backrefs[to][from] = struct{}{}
   304  	lc.changed = true
   305  }
   306  
   307  // resetLinks clears all links of the given tree.
   308  func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) {
   309  	stk := []string{from}
   310  	for len(stk) > 0 {
   311  		item := stk[len(stk)-1]
   312  		stk = stk[:len(stk)-1]
   313  
   314  		for r, refs := range lc.backrefs {
   315  			if _, ok := keep[r]; ok {
   316  				continue
   317  			}
   318  			if _, ok := refs[item]; !ok {
   319  				continue
   320  			}
   321  			lc.changed = true
   322  			delete(refs, item)
   323  			if len(refs) == 0 {
   324  				delete(lc.backrefs, r)
   325  				stk = append(stk, r)
   326  			}
   327  		}
   328  	}
   329  }