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 }