github.com/fff-chain/go-fff@v0.0.0-20220726032732-1c84420b8a99/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/fff-chain/go-fff/common/mclock" 25 "github.com/fff-chain/go-fff/p2p/enode" 26 ) 27 28 // This is the number of consecutive leaf requests that may fail before 29 // we consider re-resolving the tree root. 30 const rootRecheckFailCount = 5 31 32 // clientTree is a full tree being synced. 33 type clientTree struct { 34 c *Client 35 loc *linkEntry // link to this tree 36 37 lastRootCheck mclock.AbsTime // last revalidation of root 38 leafFailCount int 39 rootFailCount int 40 41 root *rootEntry 42 enrs *subtreeSync 43 links *subtreeSync 44 45 lc *linkCache // tracks all links between all trees 46 curLinks map[string]struct{} // links contained in this tree 47 linkGCRoot string // root on which last link GC has run 48 } 49 50 func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree { 51 return &clientTree{c: c, lc: lc, loc: loc} 52 } 53 54 // syncAll retrieves all entries of the tree. 55 func (ct *clientTree) syncAll(dest map[string]entry) error { 56 if err := ct.updateRoot(context.Background()); err != nil { 57 return err 58 } 59 if err := ct.links.resolveAll(dest); err != nil { 60 return err 61 } 62 if err := ct.enrs.resolveAll(dest); err != nil { 63 return err 64 } 65 return nil 66 } 67 68 // syncRandom retrieves a single entry of the tree. The Node return value 69 // is non-nil if the entry was a node. 70 func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) { 71 if ct.rootUpdateDue() { 72 if err := ct.updateRoot(ctx); err != nil { 73 return nil, err 74 } 75 } 76 77 // Update fail counter for leaf request errors. 78 defer func() { 79 if err != nil { 80 ct.leafFailCount++ 81 } 82 }() 83 84 // Link tree sync has priority, run it to completion before syncing ENRs. 85 if !ct.links.done() { 86 err := ct.syncNextLink(ctx) 87 return nil, err 88 } 89 ct.gcLinks() 90 91 // Sync next random entry in ENR tree. Once every node has been visited, we simply 92 // start over. This is fine because entries are cached internally by the client LRU 93 // also by DNS resolvers. 94 if ct.enrs.done() { 95 ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false) 96 } 97 return ct.syncNextRandomENR(ctx) 98 } 99 100 // canSyncRandom checks if any meaningful action can be performed by syncRandom. 101 func (ct *clientTree) canSyncRandom() bool { 102 // Note: the check for non-zero leaf count is very important here. 103 // If we're done syncing all nodes, and no leaves were found, the tree 104 // is empty and we can't use it for sync. 105 return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0 106 } 107 108 // gcLinks removes outdated links from the global link cache. GC runs once 109 // when the link sync finishes. 110 func (ct *clientTree) gcLinks() { 111 if !ct.links.done() || ct.root.lroot == ct.linkGCRoot { 112 return 113 } 114 ct.lc.resetLinks(ct.loc.str, ct.curLinks) 115 ct.linkGCRoot = ct.root.lroot 116 } 117 118 func (ct *clientTree) syncNextLink(ctx context.Context) error { 119 if len(ct.links.missing) > 1 { 120 hash := ct.links.missing[0] 121 e, err := ct.links.resolveNext(ctx, hash) 122 if err != nil { 123 return err 124 } 125 ct.links.missing = ct.links.missing[1:] 126 127 if dest, ok := e.(*linkEntry); ok { 128 ct.lc.addLink(ct.loc.str, dest.str) 129 ct.curLinks[dest.str] = struct{}{} 130 } 131 } 132 return nil 133 } 134 135 func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) { 136 index := rand.Intn(len(ct.enrs.missing)) 137 hash := ct.enrs.missing[index] 138 e, err := ct.enrs.resolveNext(ctx, hash) 139 if err != nil { 140 return nil, err 141 } 142 ct.enrs.missing = removeHash(ct.enrs.missing, index) 143 if ee, ok := e.(*enrEntry); ok { 144 return ee.node, nil 145 } 146 return nil, nil 147 } 148 149 func (ct *clientTree) String() string { 150 return ct.loc.String() 151 } 152 153 // removeHash removes the element at index from h. 154 func removeHash(h []string, index int) []string { 155 if len(h) == 1 { 156 return nil 157 } 158 last := len(h) - 1 159 if index < last { 160 h[index] = h[last] 161 h[last] = "" 162 } 163 return h[:last] 164 } 165 166 // updateRoot ensures that the given tree has an up-to-date root. 167 func (ct *clientTree) updateRoot(ctx context.Context) error { 168 if !ct.slowdownRootUpdate(ctx) { 169 return ctx.Err() 170 } 171 172 ct.lastRootCheck = ct.c.clock.Now() 173 ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout) 174 defer cancel() 175 root, err := ct.c.resolveRoot(ctx, ct.loc) 176 if err != nil { 177 ct.rootFailCount++ 178 return err 179 } 180 ct.root = &root 181 ct.rootFailCount = 0 182 ct.leafFailCount = 0 183 184 // Invalidate subtrees if changed. 185 if ct.links == nil || root.lroot != ct.links.root { 186 ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true) 187 ct.curLinks = make(map[string]struct{}) 188 } 189 if ct.enrs == nil || root.eroot != ct.enrs.root { 190 ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false) 191 } 192 return nil 193 } 194 195 // rootUpdateDue returns true when a root update is needed. 196 func (ct *clientTree) rootUpdateDue() bool { 197 tooManyFailures := ct.leafFailCount > rootRecheckFailCount 198 scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck() 199 return ct.root == nil || tooManyFailures || scheduledCheck 200 } 201 202 func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime { 203 return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval) 204 } 205 206 // slowdownRootUpdate applies a delay to root resolution if is tried 207 // too frequently. This avoids busy polling when the client is offline. 208 // Returns true if the timeout passed, false if sync was canceled. 209 func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool { 210 var delay time.Duration 211 switch { 212 case ct.rootFailCount > 20: 213 delay = 10 * time.Second 214 case ct.rootFailCount > 5: 215 delay = 5 * time.Second 216 default: 217 return true 218 } 219 timeout := ct.c.clock.NewTimer(delay) 220 defer timeout.Stop() 221 select { 222 case <-timeout.C(): 223 return true 224 case <-ctx.Done(): 225 return false 226 } 227 } 228 229 // subtreeSync is the sync of an ENR or link subtree. 230 type subtreeSync struct { 231 c *Client 232 loc *linkEntry 233 root string 234 missing []string // missing tree node hashes 235 link bool // true if this sync is for the link tree 236 leaves int // counter of synced leaves 237 } 238 239 func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync { 240 return &subtreeSync{c, loc, root, []string{root}, link, 0} 241 } 242 243 func (ts *subtreeSync) done() bool { 244 return len(ts.missing) == 0 245 } 246 247 func (ts *subtreeSync) resolveAll(dest map[string]entry) error { 248 for !ts.done() { 249 hash := ts.missing[0] 250 ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout) 251 e, err := ts.resolveNext(ctx, hash) 252 cancel() 253 if err != nil { 254 return err 255 } 256 dest[hash] = e 257 ts.missing = ts.missing[1:] 258 } 259 return nil 260 } 261 262 func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) { 263 e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash) 264 if err != nil { 265 return nil, err 266 } 267 switch e := e.(type) { 268 case *enrEntry: 269 if ts.link { 270 return nil, errENRInLinkTree 271 } 272 ts.leaves++ 273 case *linkEntry: 274 if !ts.link { 275 return nil, errLinkInENRTree 276 } 277 ts.leaves++ 278 case *branchEntry: 279 ts.missing = append(ts.missing, e.children...) 280 } 281 return e, nil 282 } 283 284 // linkCache tracks links between trees. 285 type linkCache struct { 286 backrefs map[string]map[string]struct{} 287 changed bool 288 } 289 290 func (lc *linkCache) isReferenced(r string) bool { 291 return len(lc.backrefs[r]) != 0 292 } 293 294 func (lc *linkCache) addLink(from, to string) { 295 if _, ok := lc.backrefs[to][from]; ok { 296 return 297 } 298 299 if lc.backrefs == nil { 300 lc.backrefs = make(map[string]map[string]struct{}) 301 } 302 if _, ok := lc.backrefs[to]; !ok { 303 lc.backrefs[to] = make(map[string]struct{}) 304 } 305 lc.backrefs[to][from] = struct{}{} 306 lc.changed = true 307 } 308 309 // resetLinks clears all links of the given tree. 310 func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) { 311 stk := []string{from} 312 for len(stk) > 0 { 313 item := stk[len(stk)-1] 314 stk = stk[:len(stk)-1] 315 316 for r, refs := range lc.backrefs { 317 if _, ok := keep[r]; ok { 318 continue 319 } 320 if _, ok := refs[item]; !ok { 321 continue 322 } 323 lc.changed = true 324 delete(refs, item) 325 if len(refs) == 0 { 326 delete(lc.backrefs, r) 327 stk = append(stk, r) 328 } 329 } 330 } 331 }