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

     1  package syncer
     2  
     3  import (
     4  	"bytes"
     5  	"github.com/aergoio/aergo/p2p/p2putil"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/aergoio/aergo/chain"
    10  	"github.com/aergoio/aergo/internal/enc"
    11  	"github.com/aergoio/aergo/message"
    12  	"github.com/aergoio/aergo/pkg/component"
    13  	"github.com/aergoio/aergo/types"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  type Finder struct {
    18  	compRequester component.IComponentRequester //for communicate with other service
    19  	chain         types.ChainAccessor
    20  
    21  	anchorCh chan chain.ChainAnchor
    22  	lScanCh  chan *types.BlockInfo
    23  	fScanCh  chan *message.GetHashByNoRsp
    24  
    25  	quitCh chan interface{}
    26  
    27  	lastAnchor []byte //point last block during lightscan
    28  	ctx        types.SyncContext
    29  
    30  	dfltTimeout time.Duration
    31  
    32  	cfg *SyncerConfig
    33  
    34  	isRunning bool
    35  	waitGroup *sync.WaitGroup
    36  }
    37  
    38  type FinderResult struct {
    39  	ancestor *types.BlockInfo
    40  	err      error
    41  }
    42  
    43  var (
    44  	ErrFinderQuit               = errors.New("sync finder quit")
    45  	ErrorGetSyncAncestorTimeout = errors.New("timeout for GetSyncAncestor")
    46  	ErrFinderTimeout            = errors.New("Finder timeout")
    47  	ErrAlreadySyncDone          = errors.New("Already sync done")
    48  )
    49  
    50  func newFinder(ctx *types.SyncContext, compRequester component.IComponentRequester, chain types.ChainAccessor, cfg *SyncerConfig) *Finder {
    51  	finder := &Finder{ctx: *ctx, compRequester: compRequester, chain: chain, cfg: cfg}
    52  
    53  	finder.dfltTimeout = cfg.fetchTimeOut
    54  	finder.quitCh = make(chan interface{})
    55  	finder.lScanCh = make(chan *types.BlockInfo)
    56  	finder.lScanCh = make(chan *types.BlockInfo)
    57  	finder.fScanCh = make(chan *message.GetHashByNoRsp)
    58  
    59  	return finder
    60  }
    61  
    62  //TODO refactoring: move logic to SyncContext (sync Object)
    63  func (finder *Finder) start() {
    64  	finder.waitGroup = &sync.WaitGroup{}
    65  	finder.waitGroup.Add(1)
    66  	finder.isRunning = true
    67  
    68  	run := func() {
    69  		var ancestor *types.BlockInfo
    70  		var err error
    71  
    72  		defer RecoverSyncer(NameFinder, finder.GetSeq(), finder.compRequester, func() { finder.waitGroup.Done() })
    73  
    74  		logger.Debug().Msg("start to find common ancestor")
    75  
    76  		//1. light sync
    77  		//   gather summary of my chain nodes, runTask searching ancestor to remote node
    78  		ancestor, err = finder.lightscan()
    79  
    80  		//2. heavy sync
    81  		//	 full binary search in my chain
    82  		if ancestor == nil && err == nil {
    83  			ancestor, err = finder.fullscan()
    84  		}
    85  
    86  		if err != nil {
    87  			logger.Debug().Msg("quit finder")
    88  			stopSyncer(finder.compRequester, finder.GetSeq(), NameFinder, err)
    89  			return
    90  		}
    91  
    92  		finder.compRequester.TellTo(message.SyncerSvc, &message.FinderResult{Seq: finder.GetSeq(), Ancestor: ancestor, Err: nil})
    93  		logger.Info().Msg("stopped finder successfully")
    94  	}
    95  
    96  	go run()
    97  }
    98  
    99  func (finder *Finder) stop() {
   100  	if finder == nil {
   101  		return
   102  	}
   103  
   104  	logger.Info().Msg("finder stop#1")
   105  
   106  	if finder.isRunning {
   107  		logger.Debug().Msg("finder closed quitChannel")
   108  
   109  		close(finder.quitCh)
   110  		finder.isRunning = false
   111  	}
   112  
   113  	finder.waitGroup.Wait()
   114  
   115  	logger.Info().Msg("finder stop#2")
   116  }
   117  
   118  func (finder *Finder) GetSeq() uint64 {
   119  	return finder.ctx.Seq
   120  }
   121  
   122  func (finder *Finder) GetHashByNoRsp(rsp *message.GetHashByNoRsp) {
   123  	finder.fScanCh <- rsp
   124  }
   125  
   126  func (finder *Finder) lightscan() (*types.BlockInfo, error) {
   127  	if finder.cfg.useFullScanOnly {
   128  		finder.ctx.LastAnchor = finder.ctx.BestNo + 1
   129  		return nil, nil
   130  	}
   131  
   132  	var ancestor *types.BlockInfo
   133  
   134  	anchors, err := finder.getAnchors()
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	ancestor, err = finder.getAncestor(anchors)
   140  
   141  	if ancestor == nil {
   142  		logger.Debug().Msg("not found ancestor in lightscan")
   143  	} else {
   144  		logger.Info().Str("hash", enc.ToString(ancestor.Hash)).Uint64("no", ancestor.No).Msg("find ancestor in lightscan")
   145  
   146  		if ancestor.No >= finder.ctx.TargetNo {
   147  			logger.Info().Msg("already synchronized")
   148  			return nil, ErrAlreadySyncDone
   149  		}
   150  	}
   151  
   152  	return ancestor, err
   153  }
   154  
   155  func (finder *Finder) getAnchors() ([][]byte, error) {
   156  	result, err := finder.compRequester.RequestToFutureResult(message.ChainSvc, &message.GetAnchors{finder.GetSeq()}, finder.dfltTimeout, "Finder/getAnchors")
   157  	if err != nil {
   158  		logger.Error().Err(err).Msg("failed to get anchors")
   159  		return nil, err
   160  	}
   161  
   162  	anchors := result.(message.GetAnchorsRsp).Hashes
   163  	if len(anchors) > 0 {
   164  		finder.ctx.LastAnchor = result.(message.GetAnchorsRsp).LastNo
   165  	}
   166  
   167  	logger.Info().Str("start", enc.ToString(anchors[0])).Int("count", len(anchors)).Uint64("last", finder.ctx.LastAnchor).Msg("get anchors from chain")
   168  
   169  	return anchors, nil
   170  }
   171  
   172  func (finder *Finder) getAncestor(anchors [][]byte) (*types.BlockInfo, error) {
   173  	//	send remote Peer
   174  	logger.Debug().Str("peer", p2putil.ShortForm(finder.ctx.PeerID)).Msg("send GetAncestor message to peer")
   175  	finder.compRequester.TellTo(message.P2PSvc, &message.GetSyncAncestor{Seq: finder.GetSeq(), ToWhom: finder.ctx.PeerID, Hashes: anchors})
   176  
   177  	timer := time.NewTimer(finder.dfltTimeout)
   178  
   179  	for {
   180  		select {
   181  		case result := <-finder.lScanCh:
   182  			//valid response
   183  			if result == nil || result.No >= finder.ctx.LastAnchor {
   184  				return result, nil
   185  			}
   186  		case <-timer.C:
   187  			logger.Error().Float64("sec", finder.dfltTimeout.Seconds()).Msg("get ancestor response timeout")
   188  			return nil, ErrorGetSyncAncestorTimeout
   189  		case <-finder.quitCh:
   190  			return nil, ErrFinderQuit
   191  		}
   192  	}
   193  }
   194  
   195  //TODO binary search scan
   196  func (finder *Finder) fullscan() (*types.BlockInfo, error) {
   197  	logger.Debug().Msg("finder fullscan")
   198  
   199  	ancestor, err := finder.binarySearch(0, finder.ctx.LastAnchor-1)
   200  	if err != nil {
   201  		logger.Error().Err(err).Msg("finder fullscan failed")
   202  		return nil, err
   203  	}
   204  
   205  	if ancestor == nil {
   206  		logger.Info().Msg("failed to search ancestor in fullscan")
   207  	} else {
   208  		logger.Info().Uint64("no", ancestor.No).Str("hash", enc.ToString(ancestor.Hash)).Msg("find ancestor in fullscan")
   209  	}
   210  
   211  	return ancestor, err
   212  }
   213  
   214  func (finder *Finder) binarySearch(left uint64, right uint64) (*types.BlockInfo, error) {
   215  	var mid uint64
   216  	var lastMatch *types.BlockInfo
   217  	for left <= right {
   218  		// get median
   219  		mid = (left + right) / 2
   220  		// request hash of median from remote
   221  		logger.Debug().Uint64("left", left).Uint64("right", right).Uint64("mid", mid).Msg("finder scan")
   222  
   223  		midHash, err := finder.chain.GetHashByNo(mid)
   224  		if err != nil {
   225  			logger.Error().Uint64("no", mid).Err(err).Msg("finder failed to get local hash")
   226  			return nil, err
   227  		}
   228  
   229  		exist, err := finder.hasSameHash(mid, midHash)
   230  		if err != nil {
   231  			logger.Error().Err(err).Msg("finder failed to check remote hash")
   232  			return nil, err
   233  		}
   234  
   235  		if exist {
   236  			left = mid + 1
   237  
   238  			lastMatch = &types.BlockInfo{Hash: midHash, No: mid}
   239  			logger.Debug().Uint64("mid", mid).Msg("matched")
   240  		} else {
   241  			if mid == 0 {
   242  				break
   243  			} else {
   244  				right = mid - 1
   245  			}
   246  		}
   247  	}
   248  
   249  	return lastMatch, nil
   250  }
   251  
   252  func (finder *Finder) hasSameHash(no types.BlockNo, localHash []byte) (bool, error) {
   253  	finder.compRequester.TellTo(message.P2PSvc, &message.GetHashByNo{Seq: finder.GetSeq(), ToWhom: finder.ctx.PeerID, BlockNo: no})
   254  
   255  	recvHashRsp := func() (*message.GetHashByNoRsp, error) {
   256  		timer := time.NewTimer(finder.dfltTimeout)
   257  
   258  		for {
   259  			select {
   260  			case result := <-finder.fScanCh:
   261  				return result, result.Err
   262  			case <-timer.C:
   263  				logger.Error().Float64("sec", finder.dfltTimeout.Seconds()).Msg("finder get response timeout")
   264  				return nil, ErrFinderTimeout
   265  			case <-finder.quitCh:
   266  				return nil, ErrFinderQuit
   267  			}
   268  		}
   269  	}
   270  
   271  	rspMsg, err := recvHashRsp()
   272  	if err != nil || rspMsg.BlockHash == nil {
   273  		logger.Error().Err(err).Msg("finder failed to get remote hash")
   274  		return false, err
   275  	}
   276  
   277  	if bytes.Equal(localHash, rspMsg.BlockHash) {
   278  		logger.Debug().Uint64("no", no).Msg("exist hash")
   279  		return true, nil
   280  	} else {
   281  		logger.Debug().Uint64("no", no).Msg("not exist hash")
   282  		return false, nil
   283  	}
   284  }