github.com/core-coin/go-core/v2@v2.1.9/p2p/dnsdisc/sync.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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/core-coin/go-core/v2/common/mclock"
    25  	"github.com/core-coin/go-core/v2/p2p/enode"
    26  )
    27  
    28  const (
    29  	rootRecheckFailCount = 5 // update root if this many leaf requests fail
    30  )
    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.
    93  	if ct.enrs.done() {
    94  		ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
    95  	}
    96  	return ct.syncNextRandomENR(ctx)
    97  }
    98  
    99  // gcLinks removes outdated links from the global link cache. GC runs once
   100  // when the link sync finishes.
   101  func (ct *clientTree) gcLinks() {
   102  	if !ct.links.done() || ct.root.lroot == ct.linkGCRoot {
   103  		return
   104  	}
   105  	ct.lc.resetLinks(ct.loc.str, ct.curLinks)
   106  	ct.linkGCRoot = ct.root.lroot
   107  }
   108  
   109  func (ct *clientTree) syncNextLink(ctx context.Context) error {
   110  	hash := ct.links.missing[0]
   111  	e, err := ct.links.resolveNext(ctx, hash)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	ct.links.missing = ct.links.missing[1:]
   116  
   117  	if dest, ok := e.(*linkEntry); ok {
   118  		ct.lc.addLink(ct.loc.str, dest.str)
   119  		ct.curLinks[dest.str] = struct{}{}
   120  	}
   121  	return nil
   122  }
   123  
   124  func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
   125  	index := rand.Intn(len(ct.enrs.missing))
   126  	hash := ct.enrs.missing[index]
   127  	e, err := ct.enrs.resolveNext(ctx, hash)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	ct.enrs.missing = removeHash(ct.enrs.missing, index)
   132  	if ee, ok := e.(*enrEntry); ok {
   133  		return ee.node, nil
   134  	}
   135  	return nil, nil
   136  }
   137  
   138  func (ct *clientTree) String() string {
   139  	return ct.loc.String()
   140  }
   141  
   142  // removeHash removes the element at index from h.
   143  func removeHash(h []string, index int) []string {
   144  	if len(h) == 1 {
   145  		return nil
   146  	}
   147  	last := len(h) - 1
   148  	if index < last {
   149  		h[index] = h[last]
   150  		h[last] = ""
   151  	}
   152  	return h[:last]
   153  }
   154  
   155  // updateRoot ensures that the given tree has an up-to-date root.
   156  func (ct *clientTree) updateRoot(ctx context.Context) error {
   157  	if !ct.slowdownRootUpdate(ctx) {
   158  		return ctx.Err()
   159  	}
   160  
   161  	ct.lastRootCheck = ct.c.clock.Now()
   162  	ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout)
   163  	defer cancel()
   164  	root, err := ct.c.resolveRoot(ctx, ct.loc)
   165  	if err != nil {
   166  		ct.rootFailCount++
   167  		return err
   168  	}
   169  	ct.root = &root
   170  	ct.rootFailCount = 0
   171  	ct.leafFailCount = 0
   172  
   173  	// Invalidate subtrees if changed.
   174  	if ct.links == nil || root.lroot != ct.links.root {
   175  		ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
   176  		ct.curLinks = make(map[string]struct{})
   177  	}
   178  	if ct.enrs == nil || root.eroot != ct.enrs.root {
   179  		ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
   180  	}
   181  	return nil
   182  }
   183  
   184  // rootUpdateDue returns true when a root update is needed.
   185  func (ct *clientTree) rootUpdateDue() bool {
   186  	tooManyFailures := ct.leafFailCount > rootRecheckFailCount
   187  	scheduledCheck := ct.c.clock.Now().Sub(ct.lastRootCheck) > ct.c.cfg.RecheckInterval
   188  	return ct.root == nil || tooManyFailures || scheduledCheck
   189  }
   190  
   191  // slowdownRootUpdate applies a delay to root resolution if is tried
   192  // too frequently. This avoids busy polling when the client is offline.
   193  // Returns true if the timeout passed, false if sync was canceled.
   194  func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool {
   195  	var delay time.Duration
   196  	switch {
   197  	case ct.rootFailCount > 20:
   198  		delay = 10 * time.Second
   199  	case ct.rootFailCount > 5:
   200  		delay = 5 * time.Second
   201  	default:
   202  		return true
   203  	}
   204  	timeout := ct.c.clock.NewTimer(delay)
   205  	defer timeout.Stop()
   206  	select {
   207  	case <-timeout.C():
   208  		return true
   209  	case <-ctx.Done():
   210  		return false
   211  	}
   212  }
   213  
   214  // subtreeSync is the sync of an ENR or link subtree.
   215  type subtreeSync struct {
   216  	c       *Client
   217  	loc     *linkEntry
   218  	root    string
   219  	missing []string // missing tree node hashes
   220  	link    bool     // true if this sync is for the link tree
   221  }
   222  
   223  func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
   224  	return &subtreeSync{c, loc, root, []string{root}, link}
   225  }
   226  
   227  func (ts *subtreeSync) done() bool {
   228  	return len(ts.missing) == 0
   229  }
   230  
   231  func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
   232  	for !ts.done() {
   233  		hash := ts.missing[0]
   234  		ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
   235  		e, err := ts.resolveNext(ctx, hash)
   236  		cancel()
   237  		if err != nil {
   238  			return err
   239  		}
   240  		dest[hash] = e
   241  		ts.missing = ts.missing[1:]
   242  	}
   243  	return nil
   244  }
   245  
   246  func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
   247  	e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	switch e := e.(type) {
   252  	case *enrEntry:
   253  		if ts.link {
   254  			return nil, errENRInLinkTree
   255  		}
   256  	case *linkEntry:
   257  		if !ts.link {
   258  			return nil, errLinkInENRTree
   259  		}
   260  	case *branchEntry:
   261  		ts.missing = append(ts.missing, e.children...)
   262  	}
   263  	return e, nil
   264  }
   265  
   266  // linkCache tracks links between trees.
   267  type linkCache struct {
   268  	backrefs map[string]map[string]struct{}
   269  	changed  bool
   270  }
   271  
   272  func (lc *linkCache) isReferenced(r string) bool {
   273  	return len(lc.backrefs[r]) != 0
   274  }
   275  
   276  func (lc *linkCache) addLink(from, to string) {
   277  	if _, ok := lc.backrefs[to][from]; ok {
   278  		return
   279  	}
   280  
   281  	if lc.backrefs == nil {
   282  		lc.backrefs = make(map[string]map[string]struct{})
   283  	}
   284  	if _, ok := lc.backrefs[to]; !ok {
   285  		lc.backrefs[to] = make(map[string]struct{})
   286  	}
   287  	lc.backrefs[to][from] = struct{}{}
   288  	lc.changed = true
   289  }
   290  
   291  // resetLinks clears all links of the given tree.
   292  func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) {
   293  	stk := []string{from}
   294  	for len(stk) > 0 {
   295  		item := stk[len(stk)-1]
   296  		stk = stk[:len(stk)-1]
   297  
   298  		for r, refs := range lc.backrefs {
   299  			if _, ok := keep[r]; ok {
   300  				continue
   301  			}
   302  			if _, ok := refs[item]; !ok {
   303  				continue
   304  			}
   305  			lc.changed = true
   306  			delete(refs, item)
   307  			if len(refs) == 0 {
   308  				delete(lc.backrefs, r)
   309  				stk = append(stk, r)
   310  			}
   311  		}
   312  	}
   313  }