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  }