github.com/phillinzzz/newBsc@v1.1.6/eth/protocols/diff/tracker.go (about)

     1  // Copyright 2021 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 diff
    18  
    19  import (
    20  	"container/list"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/phillinzzz/newBsc/log"
    26  )
    27  
    28  const (
    29  	// maxTrackedPackets is a huge number to act as a failsafe on the number of
    30  	// pending requests the node will track. It should never be hit unless an
    31  	// attacker figures out a way to spin requests.
    32  	maxTrackedPackets = 10000
    33  )
    34  
    35  // request tracks sent network requests which have not yet received a response.
    36  type request struct {
    37  	peer    string
    38  	version uint // Protocol version
    39  
    40  	reqCode uint64 // Protocol message code of the request
    41  	resCode uint64 // Protocol message code of the expected response
    42  
    43  	time   time.Time     // Timestamp when the request was made
    44  	expire *list.Element // Expiration marker to untrack it
    45  }
    46  
    47  type Tracker struct {
    48  	timeout time.Duration // Global timeout after which to drop a tracked packet
    49  
    50  	pending map[uint64]*request // Currently pending requests
    51  	expire  *list.List          // Linked list tracking the expiration order
    52  	wake    *time.Timer         // Timer tracking the expiration of the next item
    53  
    54  	lock sync.Mutex // Lock protecting from concurrent updates
    55  }
    56  
    57  func NewTracker(timeout time.Duration) *Tracker {
    58  	return &Tracker{
    59  		timeout: timeout,
    60  		pending: make(map[uint64]*request),
    61  		expire:  list.New(),
    62  	}
    63  }
    64  
    65  // Track adds a network request to the tracker to wait for a response to arrive
    66  // or until the request it cancelled or times out.
    67  func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint64, id uint64) {
    68  	t.lock.Lock()
    69  	defer t.lock.Unlock()
    70  
    71  	// If there's a duplicate request, we've just random-collided (or more probably,
    72  	// we have a bug), report it. We could also add a metric, but we're not really
    73  	// expecting ourselves to be buggy, so a noisy warning should be enough.
    74  	if _, ok := t.pending[id]; ok {
    75  		log.Error("Network request id collision", "version", version, "code", reqCode, "id", id)
    76  		return
    77  	}
    78  	// If we have too many pending requests, bail out instead of leaking memory
    79  	if pending := len(t.pending); pending >= maxTrackedPackets {
    80  		log.Error("Request tracker exceeded allowance", "pending", pending, "peer", peer, "version", version, "code", reqCode)
    81  		return
    82  	}
    83  	// Id doesn't exist yet, start tracking it
    84  	t.pending[id] = &request{
    85  		peer:    peer,
    86  		version: version,
    87  		reqCode: reqCode,
    88  		resCode: resCode,
    89  		time:    time.Now(),
    90  		expire:  t.expire.PushBack(id),
    91  	}
    92  
    93  	// If we've just inserted the first item, start the expiration timer
    94  	if t.wake == nil {
    95  		t.wake = time.AfterFunc(t.timeout, t.clean)
    96  	}
    97  }
    98  
    99  // clean is called automatically when a preset time passes without a response
   100  // being dleivered for the first network request.
   101  func (t *Tracker) clean() {
   102  	t.lock.Lock()
   103  	defer t.lock.Unlock()
   104  
   105  	// Expire anything within a certain threshold (might be no items at all if
   106  	// we raced with the delivery)
   107  	for t.expire.Len() > 0 {
   108  		// Stop iterating if the next pending request is still alive
   109  		var (
   110  			head = t.expire.Front()
   111  			id   = head.Value.(uint64)
   112  			req  = t.pending[id]
   113  		)
   114  		if time.Since(req.time) < t.timeout+5*time.Millisecond {
   115  			break
   116  		}
   117  		// Nope, dead, drop it
   118  		t.expire.Remove(head)
   119  		delete(t.pending, id)
   120  	}
   121  	t.schedule()
   122  }
   123  
   124  // schedule starts a timer to trigger on the expiration of the first network
   125  // packet.
   126  func (t *Tracker) schedule() {
   127  	if t.expire.Len() == 0 {
   128  		t.wake = nil
   129  		return
   130  	}
   131  	t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean)
   132  }
   133  
   134  // Fulfil fills a pending request, if any is available.
   135  func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) bool {
   136  	t.lock.Lock()
   137  	defer t.lock.Unlock()
   138  
   139  	// If it's a non existing request, track as stale response
   140  	req, ok := t.pending[id]
   141  	if !ok {
   142  		return false
   143  	}
   144  	// If the response is funky, it might be some active attack
   145  	if req.peer != peer || req.version != version || req.resCode != code {
   146  		log.Warn("Network response id collision",
   147  			"have", fmt.Sprintf("%s:/%d:%d", peer, version, code),
   148  			"want", fmt.Sprintf("%s:/%d:%d", peer, req.version, req.resCode),
   149  		)
   150  		return false
   151  	}
   152  	// Everything matches, mark the request serviced
   153  	t.expire.Remove(req.expire)
   154  	delete(t.pending, id)
   155  	if req.expire.Prev() == nil {
   156  		if t.wake.Stop() {
   157  			t.schedule()
   158  		}
   159  	}
   160  	return true
   161  }