github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/protocols/dag/dagstream/dagstreamleecher/leecher.go (about) 1 package dagstreamleecher 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/hash" 10 "github.com/unicornultrafoundation/go-helios/native/dag" 11 "github.com/unicornultrafoundation/go-helios/native/idx" 12 13 "github.com/unicornultrafoundation/go-u2u/gossip/protocols/dag/dagstream" 14 ) 15 16 // Leecher is responsible for requesting events based on lexicographic event streams 17 type Leecher struct { 18 *basestreamleecher.BaseLeecher 19 20 // Callbacks 21 callback Callbacks 22 23 cfg Config 24 25 // State 26 session sessionState 27 epoch idx.Epoch 28 29 emptyState bool 30 forceSyncing bool 31 paused bool 32 } 33 34 // New creates an events downloader to request events based on lexicographic event streams 35 func New(epoch idx.Epoch, emptyState bool, cfg Config, callback Callbacks) *Leecher { 36 l := &Leecher{ 37 cfg: cfg, 38 callback: callback, 39 emptyState: emptyState, 40 epoch: epoch, 41 } 42 l.BaseLeecher = basestreamleecher.New(cfg.RecheckInterval, basestreamleecher.Callbacks{ 43 SelectSessionPeerCandidates: l.selectSessionPeerCandidates, 44 ShouldTerminateSession: l.shouldTerminateSession, 45 StartSession: l.startSession, 46 TerminateSession: l.terminateSession, 47 OngoingSession: func() bool { 48 return l.session.agent != nil 49 }, 50 OngoingSessionPeer: func() string { 51 return l.session.peer 52 }, 53 }) 54 return l 55 } 56 57 type Callbacks struct { 58 IsProcessed func(hash.Event) bool 59 60 RequestChunk func(peer string, r dagstream.Request) error 61 Suspend func(peer string) bool 62 PeerEpoch func(peer string) idx.Epoch 63 } 64 65 type sessionState struct { 66 agent *basepeerleecher.BasePeerLeecher 67 peer string 68 startTime time.Time 69 endTime time.Time 70 lastReceived time.Time 71 try uint32 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 } 91 } 92 93 func (d *Leecher) Pause() { 94 d.Mu.Lock() 95 defer d.Mu.Unlock() 96 d.paused = true 97 d.terminateSession() 98 } 99 100 func (d *Leecher) Resume() { 101 d.Mu.Lock() 102 defer d.Mu.Unlock() 103 d.paused = false 104 } 105 106 func (d *Leecher) selectSessionPeerCandidates() []string { 107 if d.paused { 108 return nil 109 } 110 var selected []string 111 currentEpochPeers := make([]string, 0, len(d.Peers)) 112 futureEpochPeers := make([]string, 0, len(d.Peers)) 113 for p := range d.Peers { 114 epoch := d.callback.PeerEpoch(p) 115 if epoch == d.epoch { 116 currentEpochPeers = append(currentEpochPeers, p) 117 } 118 if epoch > d.epoch { 119 futureEpochPeers = append(futureEpochPeers, p) 120 } 121 } 122 sinceEnd := time.Since(d.session.endTime) 123 waitUntilProcessed := d.session.try == 0 || sinceEnd > d.cfg.MinSessionRestart 124 hasSomethingToSync := d.session.try == 0 || len(futureEpochPeers) > 0 || sinceEnd >= d.cfg.MaxSessionRestart || d.forceSyncing 125 if waitUntilProcessed && hasSomethingToSync { 126 if len(futureEpochPeers) > 0 && (d.session.try%5 != 4 || len(currentEpochPeers) == 0) { 127 // normally work only with peers which have a higher epoch 128 selected = futureEpochPeers 129 } else { 130 // if above doesn't work, try peers on current epoch every 5th try 131 selected = currentEpochPeers 132 } 133 } 134 return selected 135 } 136 137 func getSessionID(epoch idx.Epoch, try uint32) uint32 { 138 return (uint32(epoch) << 12) ^ try 139 } 140 141 func (d *Leecher) startSession(candidates []string) { 142 peer := candidates[rand.Intn(len(candidates))] 143 144 typ := dagstream.RequestIDs 145 if d.callback.PeerEpoch(peer) > d.epoch && d.emptyState && d.session.try == 0 { 146 typ = dagstream.RequestEvents 147 } 148 149 session := dagstream.Session{ 150 ID: getSessionID(d.epoch, d.session.try), 151 Start: d.epoch.Bytes(), 152 Stop: (d.epoch + 1).Bytes(), 153 } 154 155 d.session.agent = basepeerleecher.New(&d.Wg, d.cfg.Session, basepeerleecher.EpochDownloaderCallbacks{ 156 IsProcessed: func(id interface{}) bool { 157 return d.callback.IsProcessed(id.(hash.Event)) 158 }, 159 RequestChunks: func(maxNum uint32, maxSize uint64, chunks uint32) error { 160 return d.callback.RequestChunk(peer, 161 dagstream.Request{ 162 Session: session, 163 Limit: dag.Metric{Num: idx.Event(maxNum), Size: maxSize}, 164 Type: typ, 165 MaxChunks: chunks, 166 }) 167 }, 168 Suspend: func() bool { 169 return d.callback.Suspend(peer) 170 }, 171 Done: func() bool { 172 return false 173 }, 174 }) 175 176 now := time.Now() 177 d.session.startTime = now 178 d.session.lastReceived = now 179 d.session.endTime = now 180 d.session.try++ 181 d.session.peer = peer 182 183 d.session.agent.Start() 184 185 d.forceSyncing = false 186 } 187 188 func (d *Leecher) OnNewEpoch(myEpoch idx.Epoch) { 189 d.Mu.Lock() 190 defer d.Mu.Unlock() 191 192 if d.Terminated { 193 return 194 } 195 196 d.terminateSession() 197 198 d.epoch = myEpoch 199 d.session.try = 0 200 d.emptyState = true 201 202 d.Routine() 203 } 204 205 func (d *Leecher) ForceSyncing() { 206 d.Mu.Lock() 207 defer d.Mu.Unlock() 208 d.forceSyncing = true 209 } 210 211 func (d *Leecher) NotifyChunkReceived(sessionID uint32, last hash.Event, done bool) error { 212 d.Mu.Lock() 213 defer d.Mu.Unlock() 214 if d.session.agent == nil { 215 return nil 216 } 217 if getSessionID(d.epoch, d.session.try-1) != sessionID { 218 return nil 219 } 220 221 d.session.lastReceived = time.Now() 222 if done { 223 d.terminateSession() 224 return nil 225 } 226 return d.session.agent.NotifyChunkReceived(last) 227 }