github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/protocols/blockvotes/bvstream/bvstreamleecher/leecher.go (about) 1 package bvstreamleecher 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/idx" 11 12 "github.com/unicornultrafoundation/go-u2u/gossip/protocols/blockvotes/bvstream" 13 ) 14 15 // Leecher is responsible for requesting BVs based on lexicographic BVs streams 16 type Leecher struct { 17 *basestreamleecher.BaseLeecher 18 19 // Callbacks 20 callback Callbacks 21 22 cfg Config 23 24 // State 25 session sessionState 26 27 forceSyncing bool 28 } 29 30 // New creates an BVs downloader to request BVs based on lexicographic BVs 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 LowestBlockToDecide func() (idx.Epoch, idx.Block) 53 MaxEpochToDecide func() idx.Epoch 54 IsProcessed func(epoch idx.Epoch, lastBlock idx.Block, id hash.Event) bool 55 56 RequestChunk func(peer string, r bvstream.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 lowestBlockToDecide idx.Block 72 } 73 74 type BVsID struct { 75 Epoch idx.Epoch 76 LastBlock idx.Block 77 ID hash.Event 78 } 79 80 func (d *Leecher) shouldTerminateSession() bool { 81 if d.session.agent.Stopped() { 82 return true 83 } 84 85 noProgress := time.Since(d.session.lastReceived) >= d.cfg.BaseProgressWatchdog*time.Duration(d.session.try+5)/5 86 stuck := time.Since(d.session.startTime) >= d.cfg.BaseSessionWatchdog*time.Duration(d.session.try+5)/5 87 return stuck || noProgress 88 } 89 90 func (d *Leecher) terminateSession() { 91 // force the epoch download to end 92 if d.session.agent != nil { 93 d.session.agent.Terminate() 94 d.session.agent = nil 95 d.session.endTime = time.Now() 96 _, lowestBlockToDecide := d.callback.LowestBlockToDecide() 97 if lowestBlockToDecide >= d.session.lowestBlockToDecide+idx.Block(d.cfg.Session.DefaultChunkItemsNum) { 98 // reset the counter of unsuccessful sync attempts 99 d.session.try = 0 100 } 101 } 102 } 103 104 func (d *Leecher) selectSessionPeerCandidates() []string { 105 knowledgeablePeers := make([]string, 0, len(d.Peers)) 106 allPeers := make([]string, 0, len(d.Peers)) 107 startEpoch, startBlock := d.callback.LowestBlockToDecide() 108 endEpoch := d.callback.MaxEpochToDecide() 109 if startEpoch >= endEpoch { 110 return nil 111 } 112 for p := range d.Peers { 113 block := d.callback.PeerBlock(p) 114 if block >= startBlock { 115 knowledgeablePeers = append(knowledgeablePeers, p) 116 } 117 allPeers = append(allPeers, p) 118 } 119 sinceEnd := time.Since(d.session.endTime) 120 waitUntilProcessed := d.session.try == 0 || sinceEnd > d.cfg.MinSessionRestart 121 hasSomethingToSync := d.session.try == 0 || len(knowledgeablePeers) > 0 || sinceEnd >= d.cfg.MaxSessionRestart || d.forceSyncing 122 if waitUntilProcessed && hasSomethingToSync { 123 if len(knowledgeablePeers) > 0 && d.session.try%5 != 4 { 124 // normally work only with peers which have a higher block 125 return knowledgeablePeers 126 } else { 127 // if above doesn't work, try other peers on 5th try 128 return allPeers 129 } 130 } 131 return nil 132 } 133 134 func getSessionID(block idx.Block, try uint32) uint32 { 135 return (uint32(block) << 12) ^ try 136 } 137 138 func (d *Leecher) startSession(candidates []string) { 139 peer := candidates[rand.Intn(len(candidates))] 140 141 startEpoch, startBlock := d.callback.LowestBlockToDecide() 142 endEpoch := d.callback.MaxEpochToDecide() 143 if endEpoch <= startEpoch { 144 endEpoch = startEpoch + 1 145 } 146 session := bvstream.Session{ 147 ID: getSessionID(startBlock, d.session.try), 148 Start: bvstream.Locator(append(startEpoch.Bytes(), startBlock.Bytes()...)), 149 Stop: bvstream.Locator(endEpoch.Bytes()), 150 } 151 152 d.session.agent = basepeerleecher.New(&d.Wg, d.cfg.Session, basepeerleecher.EpochDownloaderCallbacks{ 153 IsProcessed: func(id interface{}) bool { 154 lastID := id.(BVsID) 155 return d.callback.IsProcessed(lastID.Epoch, lastID.LastBlock, lastID.ID) 156 }, 157 RequestChunks: func(maxNum uint32, maxSize uint64, chunks uint32) error { 158 return d.callback.RequestChunk(peer, 159 bvstream.Request{ 160 Session: session, 161 Limit: bvstream.Metric{Num: idx.Block(maxNum), Size: maxSize}, 162 Type: 0, 163 MaxChunks: chunks, 164 }) 165 }, 166 Suspend: func() bool { 167 return d.callback.Suspend(peer) 168 }, 169 Done: func() bool { 170 return false 171 }, 172 }) 173 174 now := time.Now() 175 d.session.startTime = now 176 d.session.lastReceived = now 177 d.session.endTime = now 178 d.session.try++ 179 d.session.peer = peer 180 d.session.sessionID = session.ID 181 d.session.lowestBlockToDecide = startBlock 182 183 d.session.agent.Start() 184 185 d.forceSyncing = false 186 } 187 188 func (d *Leecher) ForceSyncing() { 189 d.Mu.Lock() 190 defer d.Mu.Unlock() 191 d.forceSyncing = true 192 } 193 194 func (d *Leecher) NotifyChunkReceived(sessionID uint32, lastID BVsID, done bool) error { 195 d.Mu.Lock() 196 defer d.Mu.Unlock() 197 if d.session.agent == nil { 198 return nil 199 } 200 if d.session.sessionID != sessionID { 201 return nil 202 } 203 204 d.session.lastReceived = time.Now() 205 if done { 206 d.terminateSession() 207 return nil 208 } 209 return d.session.agent.NotifyChunkReceived(lastID) 210 }