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  }