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  }