github.com/ethereum-optimism/optimism/l2geth@v0.0.0-20230612200230-50b04ade19e3/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-optimism/optimism/l2geth/common/mclock"
    25  	"github.com/ethereum-optimism/optimism/l2geth/p2p/enode"
    26  )
    27  
    28  // clientTree is a full tree being synced.
    29  type clientTree struct {
    30  	c   *Client
    31  	loc *linkEntry // link to this tree
    32  
    33  	lastRootCheck mclock.AbsTime // last revalidation of root
    34  	root          *rootEntry
    35  	enrs          *subtreeSync
    36  	links         *subtreeSync
    37  
    38  	lc         *linkCache          // tracks all links between all trees
    39  	curLinks   map[string]struct{} // links contained in this tree
    40  	linkGCRoot string              // root on which last link GC has run
    41  }
    42  
    43  func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree {
    44  	return &clientTree{c: c, lc: lc, loc: loc}
    45  }
    46  
    47  // syncAll retrieves all entries of the tree.
    48  func (ct *clientTree) syncAll(dest map[string]entry) error {
    49  	if err := ct.updateRoot(); err != nil {
    50  		return err
    51  	}
    52  	if err := ct.links.resolveAll(dest); err != nil {
    53  		return err
    54  	}
    55  	if err := ct.enrs.resolveAll(dest); err != nil {
    56  		return err
    57  	}
    58  	return nil
    59  }
    60  
    61  // syncRandom retrieves a single entry of the tree. The Node return value
    62  // is non-nil if the entry was a node.
    63  func (ct *clientTree) syncRandom(ctx context.Context) (*enode.Node, error) {
    64  	if ct.rootUpdateDue() {
    65  		if err := ct.updateRoot(); err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  	// Link tree sync has priority, run it to completion before syncing ENRs.
    70  	if !ct.links.done() {
    71  		err := ct.syncNextLink(ctx)
    72  		return nil, err
    73  	}
    74  	ct.gcLinks()
    75  
    76  	// Sync next random entry in ENR tree. Once every node has been visited, we simply
    77  	// start over. This is fine because entries are cached.
    78  	if ct.enrs.done() {
    79  		ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
    80  	}
    81  	return ct.syncNextRandomENR(ctx)
    82  }
    83  
    84  // gcLinks removes outdated links from the global link cache. GC runs once
    85  // when the link sync finishes.
    86  func (ct *clientTree) gcLinks() {
    87  	if !ct.links.done() || ct.root.lroot == ct.linkGCRoot {
    88  		return
    89  	}
    90  	ct.lc.resetLinks(ct.loc.str, ct.curLinks)
    91  	ct.linkGCRoot = ct.root.lroot
    92  }
    93  
    94  func (ct *clientTree) syncNextLink(ctx context.Context) error {
    95  	hash := ct.links.missing[0]
    96  	e, err := ct.links.resolveNext(ctx, hash)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	ct.links.missing = ct.links.missing[1:]
   101  
   102  	if dest, ok := e.(*linkEntry); ok {
   103  		ct.lc.addLink(ct.loc.str, dest.str)
   104  		ct.curLinks[dest.str] = struct{}{}
   105  	}
   106  	return nil
   107  }
   108  
   109  func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
   110  	index := rand.Intn(len(ct.enrs.missing))
   111  	hash := ct.enrs.missing[index]
   112  	e, err := ct.enrs.resolveNext(ctx, hash)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	ct.enrs.missing = removeHash(ct.enrs.missing, index)
   117  	if ee, ok := e.(*enrEntry); ok {
   118  		return ee.node, nil
   119  	}
   120  	return nil, nil
   121  }
   122  
   123  func (ct *clientTree) String() string {
   124  	return ct.loc.String()
   125  }
   126  
   127  // removeHash removes the element at index from h.
   128  func removeHash(h []string, index int) []string {
   129  	if len(h) == 1 {
   130  		return nil
   131  	}
   132  	last := len(h) - 1
   133  	if index < last {
   134  		h[index] = h[last]
   135  		h[last] = ""
   136  	}
   137  	return h[:last]
   138  }
   139  
   140  // updateRoot ensures that the given tree has an up-to-date root.
   141  func (ct *clientTree) updateRoot() error {
   142  	ct.lastRootCheck = ct.c.clock.Now()
   143  	ctx, cancel := context.WithTimeout(context.Background(), ct.c.cfg.Timeout)
   144  	defer cancel()
   145  	root, err := ct.c.resolveRoot(ctx, ct.loc)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	ct.root = &root
   150  
   151  	// Invalidate subtrees if changed.
   152  	if ct.links == nil || root.lroot != ct.links.root {
   153  		ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
   154  		ct.curLinks = make(map[string]struct{})
   155  	}
   156  	if ct.enrs == nil || root.eroot != ct.enrs.root {
   157  		ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
   158  	}
   159  	return nil
   160  }
   161  
   162  // rootUpdateDue returns true when a root update is needed.
   163  func (ct *clientTree) rootUpdateDue() bool {
   164  	return ct.root == nil || time.Duration(ct.c.clock.Now()-ct.lastRootCheck) > ct.c.cfg.RecheckInterval
   165  }
   166  
   167  // subtreeSync is the sync of an ENR or link subtree.
   168  type subtreeSync struct {
   169  	c       *Client
   170  	loc     *linkEntry
   171  	root    string
   172  	missing []string // missing tree node hashes
   173  	link    bool     // true if this sync is for the link tree
   174  }
   175  
   176  func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
   177  	return &subtreeSync{c, loc, root, []string{root}, link}
   178  }
   179  
   180  func (ts *subtreeSync) done() bool {
   181  	return len(ts.missing) == 0
   182  }
   183  
   184  func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
   185  	for !ts.done() {
   186  		hash := ts.missing[0]
   187  		ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
   188  		e, err := ts.resolveNext(ctx, hash)
   189  		cancel()
   190  		if err != nil {
   191  			return err
   192  		}
   193  		dest[hash] = e
   194  		ts.missing = ts.missing[1:]
   195  	}
   196  	return nil
   197  }
   198  
   199  func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
   200  	e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	switch e := e.(type) {
   205  	case *enrEntry:
   206  		if ts.link {
   207  			return nil, errENRInLinkTree
   208  		}
   209  	case *linkEntry:
   210  		if !ts.link {
   211  			return nil, errLinkInENRTree
   212  		}
   213  	case *branchEntry:
   214  		ts.missing = append(ts.missing, e.children...)
   215  	}
   216  	return e, nil
   217  }
   218  
   219  // linkCache tracks links between trees.
   220  type linkCache struct {
   221  	backrefs map[string]map[string]struct{}
   222  	changed  bool
   223  }
   224  
   225  func (lc *linkCache) isReferenced(r string) bool {
   226  	return len(lc.backrefs[r]) != 0
   227  }
   228  
   229  func (lc *linkCache) addLink(from, to string) {
   230  	if _, ok := lc.backrefs[to][from]; ok {
   231  		return
   232  	}
   233  
   234  	if lc.backrefs == nil {
   235  		lc.backrefs = make(map[string]map[string]struct{})
   236  	}
   237  	if _, ok := lc.backrefs[to]; !ok {
   238  		lc.backrefs[to] = make(map[string]struct{})
   239  	}
   240  	lc.backrefs[to][from] = struct{}{}
   241  	lc.changed = true
   242  }
   243  
   244  // resetLinks clears all links of the given tree.
   245  func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) {
   246  	stk := []string{from}
   247  	for len(stk) > 0 {
   248  		item := stk[len(stk)-1]
   249  		stk = stk[:len(stk)-1]
   250  
   251  		for r, refs := range lc.backrefs {
   252  			if _, ok := keep[r]; ok {
   253  				continue
   254  			}
   255  			if _, ok := refs[item]; !ok {
   256  				continue
   257  			}
   258  			lc.changed = true
   259  			delete(refs, item)
   260  			if len(refs) == 0 {
   261  				delete(lc.backrefs, r)
   262  				stk = append(stk, r)
   263  			}
   264  		}
   265  	}
   266  }