github.com/ethereum/go-ethereum@v1.16.1/p2p/discover/table_reval.go (about)

     1  // Copyright 2024 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 discover
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"slices"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/common/mclock"
    26  	"github.com/ethereum/go-ethereum/p2p/enode"
    27  )
    28  
    29  const never = mclock.AbsTime(math.MaxInt64)
    30  
    31  const slowRevalidationFactor = 3
    32  
    33  // tableRevalidation implements the node revalidation process.
    34  // It tracks all nodes contained in Table, and schedules sending PING to them.
    35  type tableRevalidation struct {
    36  	fast      revalidationList
    37  	slow      revalidationList
    38  	activeReq map[enode.ID]struct{}
    39  }
    40  
    41  type revalidationResponse struct {
    42  	n          *tableNode
    43  	newRecord  *enode.Node
    44  	didRespond bool
    45  }
    46  
    47  func (tr *tableRevalidation) init(cfg *Config) {
    48  	tr.activeReq = make(map[enode.ID]struct{})
    49  	tr.fast.nextTime = never
    50  	tr.fast.interval = cfg.PingInterval
    51  	tr.fast.name = "fast"
    52  	tr.slow.nextTime = never
    53  	tr.slow.interval = cfg.PingInterval * slowRevalidationFactor
    54  	tr.slow.name = "slow"
    55  }
    56  
    57  // nodeAdded is called when the table receives a new node.
    58  func (tr *tableRevalidation) nodeAdded(tab *Table, n *tableNode) {
    59  	tr.fast.push(n, tab.cfg.Clock.Now(), &tab.rand)
    60  }
    61  
    62  // nodeRemoved is called when a node was removed from the table.
    63  func (tr *tableRevalidation) nodeRemoved(n *tableNode) {
    64  	if n.revalList == nil {
    65  		panic(fmt.Errorf("removed node %v has nil revalList", n.ID()))
    66  	}
    67  	n.revalList.remove(n)
    68  }
    69  
    70  // nodeEndpointChanged is called when a change in IP or port is detected.
    71  func (tr *tableRevalidation) nodeEndpointChanged(tab *Table, n *tableNode) {
    72  	n.isValidatedLive = false
    73  	tr.moveToList(&tr.fast, n, tab.cfg.Clock.Now(), &tab.rand)
    74  }
    75  
    76  // run performs node revalidation.
    77  // It returns the next time it should be invoked, which is used in the Table main loop
    78  // to schedule a timer. However, run can be called at any time.
    79  func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mclock.AbsTime) {
    80  	reval := func(list *revalidationList) {
    81  		if list.nextTime <= now {
    82  			if n := list.get(&tab.rand, tr.activeReq); n != nil {
    83  				tr.startRequest(tab, n)
    84  			}
    85  			// Update nextTime regardless if any requests were started because
    86  			// current value has passed.
    87  			list.schedule(now, &tab.rand)
    88  		}
    89  	}
    90  	reval(&tr.fast)
    91  	reval(&tr.slow)
    92  
    93  	return min(tr.fast.nextTime, tr.slow.nextTime)
    94  }
    95  
    96  // startRequest spawns a revalidation request for node n.
    97  func (tr *tableRevalidation) startRequest(tab *Table, n *tableNode) {
    98  	if _, ok := tr.activeReq[n.ID()]; ok {
    99  		panic(fmt.Errorf("duplicate startRequest (node %v)", n.ID()))
   100  	}
   101  	tr.activeReq[n.ID()] = struct{}{}
   102  	resp := revalidationResponse{n: n}
   103  
   104  	// Fetch the node while holding lock.
   105  	tab.mutex.Lock()
   106  	node := n.Node
   107  	tab.mutex.Unlock()
   108  
   109  	go tab.doRevalidate(resp, node)
   110  }
   111  
   112  func (tab *Table) doRevalidate(resp revalidationResponse, node *enode.Node) {
   113  	// Ping the selected node and wait for a pong response.
   114  	remoteSeq, err := tab.net.ping(node)
   115  	resp.didRespond = err == nil
   116  
   117  	// Also fetch record if the node replied and returned a higher sequence number.
   118  	if remoteSeq > node.Seq() {
   119  		newrec, err := tab.net.RequestENR(node)
   120  		if err != nil {
   121  			tab.log.Debug("ENR request failed", "id", node.ID(), "err", err)
   122  		} else {
   123  			resp.newRecord = newrec
   124  		}
   125  	}
   126  
   127  	select {
   128  	case tab.revalResponseCh <- resp:
   129  	case <-tab.closed:
   130  	}
   131  }
   132  
   133  // handleResponse processes the result of a revalidation request.
   134  func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationResponse) {
   135  	var (
   136  		now = tab.cfg.Clock.Now()
   137  		n   = resp.n
   138  		b   = tab.bucket(n.ID())
   139  	)
   140  	delete(tr.activeReq, n.ID())
   141  
   142  	// If the node was removed from the table while getting checked, we need to stop
   143  	// processing here to avoid re-adding it.
   144  	if n.revalList == nil {
   145  		return
   146  	}
   147  
   148  	// Store potential seeds in database.
   149  	// This is done via defer to avoid holding Table lock while writing to DB.
   150  	defer func() {
   151  		if n.isValidatedLive && n.livenessChecks > 5 {
   152  			tab.db.UpdateNode(resp.n.Node)
   153  		}
   154  	}()
   155  
   156  	// Remaining logic needs access to Table internals.
   157  	tab.mutex.Lock()
   158  	defer tab.mutex.Unlock()
   159  
   160  	if !resp.didRespond {
   161  		n.livenessChecks /= 3
   162  		if n.livenessChecks <= 0 {
   163  			tab.deleteInBucket(b, n.ID())
   164  		} else {
   165  			tab.log.Debug("Node revalidation failed", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
   166  			tr.moveToList(&tr.fast, n, now, &tab.rand)
   167  		}
   168  		return
   169  	}
   170  
   171  	// The node responded.
   172  	n.livenessChecks++
   173  	n.isValidatedLive = true
   174  	tab.log.Debug("Node revalidated", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
   175  	var endpointChanged bool
   176  	if resp.newRecord != nil {
   177  		_, endpointChanged = tab.bumpInBucket(b, resp.newRecord, false)
   178  	}
   179  
   180  	// Node moves to slow list if it passed and hasn't changed.
   181  	if !endpointChanged {
   182  		tr.moveToList(&tr.slow, n, now, &tab.rand)
   183  	}
   184  }
   185  
   186  // moveToList ensures n is in the 'dest' list.
   187  func (tr *tableRevalidation) moveToList(dest *revalidationList, n *tableNode, now mclock.AbsTime, rand randomSource) {
   188  	if n.revalList == dest {
   189  		return
   190  	}
   191  	if n.revalList != nil {
   192  		n.revalList.remove(n)
   193  	}
   194  	dest.push(n, now, rand)
   195  }
   196  
   197  // revalidationList holds a list nodes and the next revalidation time.
   198  type revalidationList struct {
   199  	nodes    []*tableNode
   200  	nextTime mclock.AbsTime
   201  	interval time.Duration
   202  	name     string
   203  }
   204  
   205  // get returns a random node from the queue. Nodes in the 'exclude' map are not returned.
   206  func (list *revalidationList) get(rand randomSource, exclude map[enode.ID]struct{}) *tableNode {
   207  	if len(list.nodes) == 0 {
   208  		return nil
   209  	}
   210  	for i := 0; i < len(list.nodes)*3; i++ {
   211  		n := list.nodes[rand.Intn(len(list.nodes))]
   212  		_, excluded := exclude[n.ID()]
   213  		if !excluded {
   214  			return n
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func (list *revalidationList) schedule(now mclock.AbsTime, rand randomSource) {
   221  	list.nextTime = now.Add(time.Duration(rand.Int63n(int64(list.interval))))
   222  }
   223  
   224  func (list *revalidationList) push(n *tableNode, now mclock.AbsTime, rand randomSource) {
   225  	list.nodes = append(list.nodes, n)
   226  	if list.nextTime == never {
   227  		list.schedule(now, rand)
   228  	}
   229  	n.revalList = list
   230  }
   231  
   232  func (list *revalidationList) remove(n *tableNode) {
   233  	i := slices.Index(list.nodes, n)
   234  	if i == -1 {
   235  		panic(fmt.Errorf("node %v not found in list", n.ID()))
   236  	}
   237  	list.nodes = slices.Delete(list.nodes, i, i+1)
   238  	if len(list.nodes) == 0 {
   239  		list.nextTime = never
   240  	}
   241  	n.revalList = nil
   242  }
   243  
   244  func (list *revalidationList) contains(id enode.ID) bool {
   245  	return slices.ContainsFunc(list.nodes, func(n *tableNode) bool {
   246  		return n.ID() == id
   247  	})
   248  }