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 }