github.com/aergoio/aergo@v1.3.1/p2p/txnoticetracer.go (about)

     1  /*
     2   * @file
     3   * @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package p2p
     7  
     8  import (
     9  	"github.com/aergoio/aergo-lib/log"
    10  	"github.com/aergoio/aergo/message"
    11  	"github.com/aergoio/aergo/p2p/p2pcommon"
    12  	"github.com/aergoio/aergo/types"
    13  	lru "github.com/hashicorp/golang-lru"
    14  	"time"
    15  )
    16  
    17  const (
    18  	RequiredSendCount int = 1
    19  )
    20  const create p2pcommon.ReportType = iota + 1000
    21  var peerIDHolder = []types.PeerID(nil)
    22  
    23  type txNoticeTracer struct {
    24  	logger *log.Logger
    25  	actor  p2pcommon.ActorService
    26  	reqCnt int
    27  
    28  	txSendStats *lru.Cache
    29  	reportC     chan txNoticeSendReport
    30  
    31  	retryIDs []types.TxID
    32  	retryC   <-chan time.Time
    33  
    34  	finish chan int
    35  }
    36  
    37  var _ p2pcommon.TxNoticeTracer = (*txNoticeTracer)(nil)
    38  
    39  func newTxNoticeTracer(logger *log.Logger, actor p2pcommon.ActorService) *txNoticeTracer {
    40  	t := &txNoticeTracer{logger: logger, actor: actor, reqCnt: RequiredSendCount, reportC: make(chan txNoticeSendReport, syncManagerChanSize), finish:make(chan int)}
    41  	var err error
    42  	t.txSendStats, err = lru.New(DefaultGlobalTxCacheSize * 4)
    43  	if err != nil {
    44  		panic("Failed to create p2p trace cache " + err.Error())
    45  	}
    46  	t.retryC = time.NewTicker(time.Minute >> 1).C
    47  	return t
    48  }
    49  
    50  type txNoticeSendStat struct {
    51  	hash     types.TxID
    52  	created  time.Time
    53  	accessed time.Time
    54  	remain   int
    55  	sentCnt  int
    56  	sent     []types.PeerID
    57  }
    58  
    59  type txNoticeSendReport struct {
    60  	tType  p2pcommon.ReportType
    61  	hashes []types.TxID
    62  	expect int
    63  	peerIDs []types.PeerID
    64  }
    65  
    66  func (t *txNoticeTracer) run() {
    67  	t.logger.Info().Msg("starting p2p txNoticeTracer")
    68  	cleanUpT := time.NewTicker(time.Minute * 10)
    69  TRACE_LOOP:
    70  	for {
    71  		select {
    72  		case rep := <-t.reportC:
    73  			if rep.tType == create {
    74  				t.newTrace(rep)
    75  			} else {
    76  				t.handleReport(rep)
    77  			}
    78  		case <-t.retryC:
    79  			t.retryNotice()
    80  		case <-cleanUpT.C:
    81  			t.cleanupStales()
    82  		case <-t.finish:
    83  			break TRACE_LOOP
    84  		}
    85  	}
    86  	t.logger.Info().Msg("txNoticeTracer is finished")
    87  }
    88  func (t *txNoticeTracer) Start() {
    89  	go t.run()
    90  }
    91  func (t *txNoticeTracer) Stop() {
    92  	close(t.finish)
    93  }
    94  
    95  func (t *txNoticeTracer) RegisterTxNotice(txIDs []types.TxID, cnt int, alreadySent []types.PeerID) {
    96  	t.reportC <- txNoticeSendReport{create, txIDs, cnt, peerIDHolder}
    97  }
    98  
    99  func (t *txNoticeTracer) ReportNotSend(txIDs []types.TxID, cnt int) {
   100  	t.reportC <- txNoticeSendReport{p2pcommon.Fail, txIDs, cnt, peerIDHolder}
   101  }
   102  
   103  func (t *txNoticeTracer) ReportSend(txIDs []types.TxID, peerID types.PeerID) {
   104  	t.reportC <- txNoticeSendReport{p2pcommon.Send, txIDs, 0, []types.PeerID{peerID}}
   105  }
   106  
   107  func (t *txNoticeTracer) newTrace(report txNoticeSendReport) {
   108  	if report.expect == 0 {
   109  		t.retryIDs = append(t.retryIDs, report.hashes...)
   110  		t.logger.Debug().Array("txs", types.NewLogTxIDsMarshaller(t.retryIDs, 10)).Msg("no active peer to send notice. retrying later")
   111  	} else {
   112  		t.logger.Debug().Array("txs", types.NewLogTxIDsMarshaller(t.retryIDs, 10)).Int("toSendCnt", report.expect).Msg("new tx notice trace")
   113  		ctime := time.Now()
   114  		for _, txHash := range report.hashes {
   115  			t.txSendStats.Add(txHash, &txNoticeSendStat{hash: txHash, created: ctime, accessed: ctime, remain: report.expect})
   116  		}
   117  	}
   118  }
   119  
   120  func (t *txNoticeTracer) handleReport(report txNoticeSendReport) {
   121  	//t.logger.Debug().Str("type", report.tType.String()).Array("txs", types.NewLogTxIDsMarshaller(t.retryIDs,10)).Int("peerCnt", report.peerCnt).Msg("new tx notice trace")
   122  	for _, txHash := range report.hashes {
   123  		s, exist := t.txSendStats.Get(txHash)
   124  		if !exist { // evicted
   125  			continue
   126  		}
   127  		stat := s.(*txNoticeSendStat)
   128  		stat.remain--
   129  		if report.tType == p2pcommon.Send {
   130  			stat.sentCnt++
   131  		}
   132  		if stat.remain == 0 {
   133  			t.txSendStats.Remove(txHash)
   134  			if stat.sentCnt < t.reqCnt { // couldn't send any nodes
   135  				t.retryIDs = append(t.retryIDs, txHash)
   136  			}
   137  		} else {
   138  			stat.accessed = time.Now()
   139  		}
   140  	}
   141  
   142  }
   143  
   144  func (t *txNoticeTracer) retryNotice() {
   145  	if len(t.retryIDs) == 0 {
   146  		return
   147  	}
   148  	t.logger.Debug().Array("txs", types.NewLogTxIDsMarshaller(t.retryIDs, 10)).Msg("retrying to send tx notices")
   149  	hMap := make(map[types.TxID]int, len(t.retryIDs))
   150  	hashes := make([]types.TxID, 0, len(t.retryIDs))
   151  	for _, hash := range t.retryIDs {
   152  		if _, exist := hMap[hash]; !exist {
   153  			hashes = append(hashes, hash)
   154  			hMap[hash] = 1
   155  		}
   156  	}
   157  	// clear
   158  	t.retryIDs = t.retryIDs[:0]
   159  	if len(hashes) > 0 {
   160  		t.actor.TellRequest(message.P2PSvc, notifyNewTXs{hashes, nil})
   161  	}
   162  }
   163  
   164  func (t *txNoticeTracer) cleanupStales() {
   165  	t.logger.Debug().Msg("Cleaning up TX notice stats ")
   166  	// It should be nothing or very few stats remains in cleanup time. If not, find bugs .
   167  	expireTime := time.Now().Add(-1 * time.Minute * 10)
   168  	keys := t.txSendStats.Keys()
   169  	size := len(keys)
   170  	if size > 1000 {
   171  		size = 1000
   172  	}
   173  	toRetries := make([]types.TxID, 0, 10)
   174  	for i := 0; i < size; i++ {
   175  		s, found := t.txSendStats.Get(keys[i])
   176  		if !found {
   177  			continue
   178  		}
   179  		stat := s.(*txNoticeSendStat)
   180  		if !stat.accessed.Before(expireTime) {
   181  			break
   182  		}
   183  		if stat.sentCnt == 0 {
   184  			toRetries = append(toRetries, stat.hash)
   185  		}
   186  		t.txSendStats.Remove(keys[i])
   187  	}
   188  	if len(toRetries) > 0 {
   189  		t.logger.Info().Int("cnt", len(toRetries)).Msg("Unsent TX notices are found")
   190  		t.retryIDs = append(t.retryIDs, toRetries...)
   191  	} else {
   192  		t.logger.Debug().Msg("no unsent TX notices are found")
   193  	}
   194  }