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