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 }