github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/protocols/blockrecords/brstream/brstreamleecher/leecher.go (about) 1 package brstreamleecher 2 3 import ( 4 "math/rand" 5 "time" 6 7 "github.com/unicornultrafoundation/go-helios/gossip/basestream/basestreamleecher" 8 "github.com/unicornultrafoundation/go-helios/gossip/basestream/basestreamleecher/basepeerleecher" 9 "github.com/unicornultrafoundation/go-helios/native/idx" 10 11 "github.com/unicornultrafoundation/go-u2u/gossip/protocols/blockrecords/brstream" 12 ) 13 14 // Leecher is responsible for requesting BRs based on lexicographic BRs streams 15 type Leecher struct { 16 *basestreamleecher.BaseLeecher 17 18 // Callbacks 19 callback Callbacks 20 21 cfg Config 22 23 // State 24 session sessionState 25 26 forceSyncing bool 27 paused bool 28 } 29 30 // New creates an BRs downloader to request BRs based on lexicographic BRs streams 31 func New(cfg Config, callback Callbacks) *Leecher { 32 l := &Leecher{ 33 cfg: cfg, 34 callback: callback, 35 } 36 l.BaseLeecher = basestreamleecher.New(cfg.RecheckInterval, basestreamleecher.Callbacks{ 37 SelectSessionPeerCandidates: l.selectSessionPeerCandidates, 38 ShouldTerminateSession: l.shouldTerminateSession, 39 StartSession: l.startSession, 40 TerminateSession: l.terminateSession, 41 OngoingSession: func() bool { 42 return l.session.agent != nil 43 }, 44 OngoingSessionPeer: func() string { 45 return l.session.peer 46 }, 47 }) 48 return l 49 } 50 51 type Callbacks struct { 52 LowestBlockToFill func() idx.Block 53 MaxBlockToFill func() idx.Block 54 IsProcessed func(lastBlock idx.Block) bool 55 56 RequestChunk func(peer string, r brstream.Request) error 57 Suspend func(peer string) bool 58 PeerBlock func(peer string) idx.Block 59 } 60 61 type sessionState struct { 62 agent *basepeerleecher.BasePeerLeecher 63 peer string 64 startTime time.Time 65 endTime time.Time 66 lastReceived time.Time 67 try uint32 68 69 sessionID uint32 70 71 lowestBlockToFill idx.Block 72 } 73 74 func (d *Leecher) shouldTerminateSession() bool { 75 if d.paused || d.session.agent.Stopped() { 76 return true 77 } 78 79 noProgress := time.Since(d.session.lastReceived) >= d.cfg.BaseProgressWatchdog*time.Duration(d.session.try+5)/5 80 stuck := time.Since(d.session.startTime) >= d.cfg.BaseSessionWatchdog*time.Duration(d.session.try+5)/5 81 return stuck || noProgress 82 } 83 84 func (d *Leecher) terminateSession() { 85 // force the epoch download to end 86 if d.session.agent != nil { 87 d.session.agent.Terminate() 88 d.session.agent = nil 89 d.session.endTime = time.Now() 90 if d.callback.LowestBlockToFill() >= d.session.lowestBlockToFill+idx.Block(d.cfg.Session.DefaultChunkItemsNum) { 91 // reset the counter of unsuccessful sync attempts 92 d.session.try = 0 93 } 94 } 95 } 96 97 func (d *Leecher) Pause() { 98 d.Mu.Lock() 99 defer d.Mu.Unlock() 100 d.paused = true 101 d.terminateSession() 102 } 103 104 func (d *Leecher) Resume() { 105 d.Mu.Lock() 106 defer d.Mu.Unlock() 107 d.paused = false 108 } 109 110 func (d *Leecher) selectSessionPeerCandidates() []string { 111 if d.paused { 112 return nil 113 } 114 knowledgeablePeers := make([]string, 0, len(d.Peers)) 115 allPeers := make([]string, 0, len(d.Peers)) 116 start := d.callback.LowestBlockToFill() 117 if start >= d.callback.MaxBlockToFill() { 118 return nil 119 } 120 for p := range d.Peers { 121 block := d.callback.PeerBlock(p) 122 if block >= start { 123 knowledgeablePeers = append(knowledgeablePeers, p) 124 } 125 allPeers = append(allPeers, p) 126 } 127 sinceEnd := time.Since(d.session.endTime) 128 waitUntilProcessed := d.session.try == 0 || sinceEnd > d.cfg.MinSessionRestart 129 hasSomethingToSync := d.session.try == 0 || len(knowledgeablePeers) > 0 || sinceEnd >= d.cfg.MaxSessionRestart || d.forceSyncing 130 if waitUntilProcessed && hasSomethingToSync { 131 if len(knowledgeablePeers) > 0 && d.session.try%5 != 4 { 132 // normally work only with peers which have a higher block 133 return knowledgeablePeers 134 } else { 135 // if above doesn't work, try other peers on 5th try 136 return allPeers 137 } 138 } 139 return nil 140 } 141 142 func getSessionID(block idx.Block, try uint32) uint32 { 143 return (uint32(block) << 12) ^ try 144 } 145 146 func (d *Leecher) startSession(candidates []string) { 147 peer := candidates[rand.Intn(len(candidates))] 148 149 start := d.callback.LowestBlockToFill() 150 end := d.callback.MaxBlockToFill() 151 if end <= start { 152 end = start + 1 153 } 154 session := brstream.Session{ 155 ID: getSessionID(start, d.session.try), 156 Start: brstream.Locator(start), 157 Stop: brstream.Locator(end), 158 } 159 160 d.session.agent = basepeerleecher.New(&d.Wg, d.cfg.Session, basepeerleecher.EpochDownloaderCallbacks{ 161 IsProcessed: func(id interface{}) bool { 162 lastBlock := id.(idx.Block) 163 return d.callback.IsProcessed(lastBlock) 164 }, 165 RequestChunks: func(maxNum uint32, maxSize uint64, chunks uint32) error { 166 return d.callback.RequestChunk(peer, 167 brstream.Request{ 168 Session: session, 169 Limit: brstream.Metric{Num: idx.Block(maxNum), Size: maxSize}, 170 Type: 0, 171 MaxChunks: chunks, 172 }) 173 }, 174 Suspend: func() bool { 175 return d.callback.Suspend(peer) 176 }, 177 Done: func() bool { 178 return false 179 }, 180 }) 181 182 now := time.Now() 183 d.session.startTime = now 184 d.session.lastReceived = now 185 d.session.endTime = now 186 d.session.try++ 187 d.session.peer = peer 188 d.session.sessionID = session.ID 189 d.session.lowestBlockToFill = start 190 191 d.session.agent.Start() 192 193 d.forceSyncing = false 194 } 195 196 func (d *Leecher) ForceSyncing() { 197 d.Mu.Lock() 198 defer d.Mu.Unlock() 199 d.forceSyncing = true 200 } 201 202 func (d *Leecher) NotifyChunkReceived(sessionID uint32, lastBlock idx.Block, done bool) error { 203 d.Mu.Lock() 204 defer d.Mu.Unlock() 205 if d.session.agent == nil { 206 return nil 207 } 208 if d.session.sessionID != sessionID { 209 return nil 210 } 211 212 d.session.lastReceived = time.Now() 213 if done { 214 d.terminateSession() 215 return nil 216 } 217 return d.session.agent.NotifyChunkReceived(lastBlock) 218 }