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  }