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