github.com/aergoio/aergo@v1.3.1/syncer/hashfetcher.go (about)

     1  package syncer
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/aergoio/aergo/internal/enc"
     8  	"github.com/aergoio/aergo/message"
     9  	"github.com/aergoio/aergo/pkg/component"
    10  	"github.com/aergoio/aergo/types"
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  type HashFetcher struct {
    15  	compRequester component.IComponentRequester //for communicate with other service
    16  
    17  	ctx *types.SyncContext
    18  
    19  	responseCh chan *message.GetHashesRsp //HashSet response channel (<- Syncer)
    20  	resultCh   chan *HashSet              //BlockFetcher input channel (-> BlockFetcher)
    21  	//HashFetcher can wait in resultCh
    22  	quitCh chan interface{}
    23  
    24  	lastBlockInfo *types.BlockInfo
    25  	reqCount      uint64
    26  	reqTime       time.Time
    27  	isRequesting  bool
    28  
    29  	maxHashReq uint64
    30  	name       string
    31  
    32  	timeout time.Duration
    33  	debug   bool
    34  
    35  	isRunning bool
    36  
    37  	waitGroup *sync.WaitGroup
    38  }
    39  
    40  type HashSet struct {
    41  	Count   int
    42  	Hashes  []message.BlockHash
    43  	StartNo types.BlockNo
    44  }
    45  
    46  type HashRequest struct {
    47  	prevInfo *types.BlockInfo
    48  	count    uint64
    49  }
    50  
    51  var (
    52  	dfltTimeout     = time.Second * 180
    53  	DfltHashReqSize = uint64(1000)
    54  )
    55  
    56  var (
    57  	ErrQuitHashFetcher    = errors.New("Hashfetcher quit")
    58  	ErrInvalidHashSet     = errors.New("Invalid hash set reply")
    59  	ErrHashFetcherTimeout = errors.New("HashFetcher response timeout")
    60  )
    61  
    62  func newHashFetcher(ctx *types.SyncContext, compRequester component.IComponentRequester, bfCh chan *HashSet, cfg *SyncerConfig) *HashFetcher {
    63  	hf := &HashFetcher{ctx: ctx, compRequester: compRequester, name: NameHashFetcher}
    64  
    65  	hf.quitCh = make(chan interface{})
    66  	hf.responseCh = make(chan *message.GetHashesRsp)
    67  
    68  	hf.resultCh = bfCh
    69  
    70  	hf.lastBlockInfo = &types.BlockInfo{Hash: ctx.CommonAncestor.GetHash(), No: ctx.CommonAncestor.BlockNo()}
    71  
    72  	hf.maxHashReq = cfg.maxHashReqSize
    73  
    74  	hf.timeout = dfltTimeout
    75  
    76  	return hf
    77  }
    78  
    79  func (hf *HashFetcher) setTimeout(timeout time.Duration) {
    80  	hf.timeout = timeout
    81  }
    82  
    83  func (hf *HashFetcher) recover() {
    84  
    85  }
    86  func (hf *HashFetcher) Start() {
    87  	hf.waitGroup = &sync.WaitGroup{}
    88  	hf.waitGroup.Add(1)
    89  
    90  	hf.isRunning = true
    91  
    92  	run := func() {
    93  		defer RecoverSyncer(NameHashFetcher, hf.GetSeq(), hf.compRequester, func() { hf.waitGroup.Done() })
    94  
    95  		logger.Debug().Msg("start hash fetcher")
    96  
    97  		timer := time.NewTimer(hf.timeout)
    98  
    99  		hf.requestHashSet()
   100  
   101  		for {
   102  			select {
   103  			case msg, ok := <-hf.responseCh:
   104  				if !ok {
   105  					logger.Error().Msg("HashFetcher responseCh is closed. Syncer is stopping now")
   106  					return
   107  				}
   108  				logger.Debug().Msg("process GetHashesRsp")
   109  
   110  				timer.Stop()
   111  				res, err := hf.isValidResponse(msg)
   112  				if res {
   113  					HashSet := &HashSet{Count: len(msg.Hashes), Hashes: msg.Hashes, StartNo: msg.PrevInfo.No + 1}
   114  
   115  					if err := hf.processHashSet(HashSet); err != nil {
   116  						//TODO send errmsg to syncer & stop sync
   117  						logger.Error().Err(err).Msg("error! process hash chunk, HashFetcher exited")
   118  						if err != ErrQuitHashFetcher {
   119  							stopSyncer(hf.compRequester, hf.GetSeq(), hf.name, err)
   120  						}
   121  						return
   122  					}
   123  
   124  					if hf.isFinished(HashSet) {
   125  						closeFetcher(hf.compRequester, hf.GetSeq(), hf.name)
   126  						logger.Info().Msg("HashFetcher finished")
   127  						return
   128  					}
   129  					hf.requestHashSet()
   130  				} else if err != nil {
   131  					stopSyncer(hf.compRequester, hf.GetSeq(), hf.name, err)
   132  				}
   133  
   134  				//timer restart
   135  				timer.Reset(hf.timeout)
   136  			case <-timer.C:
   137  				if hf.requestTimeout() {
   138  					logger.Error().Msg("HashFetcher response timeout.")
   139  					stopSyncer(hf.compRequester, hf.GetSeq(), hf.name, ErrHashFetcherTimeout)
   140  				}
   141  
   142  			case <-hf.quitCh:
   143  				logger.Info().Msg("HashFetcher exited")
   144  				return
   145  			}
   146  		}
   147  	}
   148  
   149  	go run()
   150  }
   151  
   152  func (hf *HashFetcher) GetSeq() uint64 {
   153  	return hf.ctx.Seq
   154  }
   155  
   156  func (hf *HashFetcher) isFinished(HashSet *HashSet) bool {
   157  	return (hf.lastBlockInfo.No == hf.ctx.TargetNo)
   158  }
   159  
   160  func (hf *HashFetcher) requestTimeout() bool {
   161  	return hf.isRequesting && time.Now().Sub(hf.reqTime) > hf.timeout
   162  }
   163  
   164  func (hf *HashFetcher) requestHashSet() {
   165  	count := hf.maxHashReq
   166  	if hf.ctx.TargetNo < hf.lastBlockInfo.No+hf.maxHashReq {
   167  		count = hf.ctx.TargetNo - hf.lastBlockInfo.No
   168  	}
   169  
   170  	hf.reqCount = count
   171  	hf.reqTime = time.Now()
   172  	hf.isRequesting = true
   173  
   174  	logger.Debug().Uint64("prev", hf.lastBlockInfo.No).Str("prevhash", enc.ToString(hf.lastBlockInfo.Hash)).Uint64("count", count).Msg("request hashset to peer")
   175  
   176  	hf.compRequester.TellTo(message.P2PSvc, &message.GetHashes{Seq: hf.GetSeq(), ToWhom: hf.ctx.PeerID, PrevInfo: hf.lastBlockInfo, Count: count})
   177  }
   178  
   179  func (hf *HashFetcher) processHashSet(hashSet *HashSet) error {
   180  	//get HashSet reply
   181  	lastHash := hashSet.Hashes[len(hashSet.Hashes)-1]
   182  	lastHashNo := hashSet.StartNo + uint64(hashSet.Count) - 1
   183  
   184  	if lastHashNo > hf.ctx.TargetNo {
   185  		logger.Error().Uint64("target", hf.ctx.TargetNo).Uint64("last", lastHashNo).Msg("invalid hashset reponse")
   186  		return ErrInvalidHashSet
   187  	}
   188  
   189  	hf.lastBlockInfo = &types.BlockInfo{Hash: lastHash, No: lastHashNo}
   190  
   191  	//total HashSet in memory can be 3 (network + resultCh + blockFetcher)
   192  	select {
   193  	case hf.resultCh <- hashSet:
   194  	case <-hf.quitCh:
   195  		logger.Info().Msg("hash fetcher quit while pushing result")
   196  		return ErrQuitHashFetcher
   197  	}
   198  
   199  	hf.isRequesting = false
   200  
   201  	logger.Debug().Uint64("target", hf.ctx.TargetNo).Uint64("start", hashSet.StartNo).Uint64("last", lastHashNo).Int("count", len(hashSet.Hashes)).Msg("push hashset to BlockFetcher")
   202  
   203  	return nil
   204  }
   205  
   206  func (hf *HashFetcher) stop() {
   207  	if hf == nil {
   208  		return
   209  	}
   210  
   211  	if hf.isRunning {
   212  		logger.Info().Msg("HashFetcher stop#1")
   213  
   214  		close(hf.quitCh)
   215  		logger.Info().Msg("HashFetcher close quitCh")
   216  
   217  		close(hf.responseCh)
   218  
   219  		hf.waitGroup.Wait()
   220  		hf.isRunning = false
   221  	}
   222  	logger.Info().Msg("HashFetcher stopped")
   223  }
   224  
   225  func (hf *HashFetcher) isValidResponse(msg *message.GetHashesRsp) (bool, error) {
   226  	isValid := true
   227  	var err error
   228  
   229  	if msg == nil {
   230  		panic("nil message error")
   231  	}
   232  
   233  	if msg.Err != nil {
   234  		logger.Error().Err(msg.Err).Msg("receive GetHashesRsp with error")
   235  		err = msg.Err
   236  		isValid = false
   237  	}
   238  
   239  	if !hf.lastBlockInfo.Equal(msg.PrevInfo) || hf.reqCount != msg.Count {
   240  		isValid = false
   241  	}
   242  
   243  	if !isValid {
   244  		logger.Error().Str("req prev", enc.ToString(hf.lastBlockInfo.Hash)).
   245  			Str("msg prev", enc.ToString(msg.PrevInfo.Hash)).
   246  			Uint64("req count", hf.reqCount).
   247  			Uint64("msg count", msg.Count).
   248  			Msg("invalid GetHashesRsp")
   249  		return false, err
   250  	}
   251  
   252  	return true, nil
   253  }
   254  
   255  func (hf *HashFetcher) GetHahsesRsp(msg *message.GetHashesRsp) {
   256  	if hf == nil {
   257  		return
   258  	}
   259  
   260  	count := len(msg.Hashes)
   261  
   262  	if count == 0 {
   263  		logger.Error().Int("count", count).
   264  			Uint64("prev", msg.PrevInfo.No).Msg("receive empty GetHashesRsp")
   265  		return
   266  	}
   267  
   268  	logger.Debug().Int("count", count).
   269  		Uint64("prev", msg.PrevInfo.No).
   270  		Str("start", enc.ToString(msg.Hashes[0])).
   271  		Str("end", enc.ToString(msg.Hashes[count-1])).Msg("receive GetHashesRsp")
   272  
   273  	hf.responseCh <- msg
   274  	return
   275  }