OSDN Git Service

edit the addressBook
[bytom/bytom.git] / p2p / addrbook.go
1 // Modified for Tendermint
2 // Originally Copyright (c) 2013-2014 Conformal Systems LLC.
3 // https://github.com/conformal/btcd/blob/master/LICENSE
4
5 package p2p
6
7 import (
8         "encoding/binary"
9         "encoding/json"
10         "math"
11         "math/rand"
12         "net"
13         "os"
14         "sync"
15         "time"
16
17         log "github.com/sirupsen/logrus"
18         "github.com/tendermint/go-crypto"
19         cmn "github.com/tendermint/tmlibs/common"
20 )
21
22 const (
23         // addresses under which the address manager will claim to need more addresses.
24         needAddressThreshold = 1000
25
26         // interval used to dump the address cache to disk for future use.
27         dumpAddressInterval = time.Minute * 2
28
29         // max addresses in each old address bucket.
30         oldBucketSize = 64
31
32         // buckets we split old addresses over.
33         oldBucketCount = 64
34
35         // max addresses in each new address bucket.
36         newBucketSize = 64
37
38         // buckets that we spread new addresses over.
39         newBucketCount = 256
40
41         // old buckets over which an address group will be spread.
42         oldBucketsPerGroup = 4
43
44         // new buckets over which an source address group will be spread.
45         newBucketsPerGroup = 32
46
47         // buckets a frequently seen new address may end up in.
48         maxNewBucketsPerAddress = 4
49
50         // days before which we assume an address has vanished
51         // if we have not seen it announced in that long.
52         numMissingDays = 30
53
54         // tries without a single success before we assume an address is bad.
55         numRetries = 3
56
57         // max failures we will accept without a success before considering an address bad.
58         maxFailures = 10
59
60         // days since the last success before we will consider evicting an address.
61         minBadDays = 7
62
63         // % of total addresses known returned by GetSelection.
64         getSelectionPercent = 23
65
66         // min addresses that must be returned by GetSelection. Useful for bootstrapping.
67         minGetSelection = 32
68
69         // max addresses returned by GetSelection
70         // NOTE: this must match "maxPexMessageSize"
71         maxGetSelection = 250
72 )
73
74 const (
75         bucketTypeNew = 0x01
76         bucketTypeOld = 0x02
77 )
78
79 // AddrBook - concurrency safe peer address manager.
80 type AddrBook struct {
81         cmn.BaseService
82
83         // immutable after creation
84         filePath          string
85         routabilityStrict bool
86         key               string
87
88         mtx        sync.RWMutex
89         rand       *rand.Rand
90         ourAddrs   map[string]*NetAddress
91         addrLookup map[string]*knownAddress // new & old
92         bucketsNew []map[string]*knownAddress
93         bucketsOld []map[string]*knownAddress
94         nOld       int
95         nNew       int
96 }
97
98 // NewAddrBook creates a new address book. Use Start to begin processing asynchronous address updates.
99 func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
100         a := &AddrBook{
101                 filePath:          filePath,
102                 routabilityStrict: routabilityStrict,
103                 key:               crypto.CRandHex(24),
104                 rand:              rand.New(rand.NewSource(time.Now().UnixNano())),
105                 ourAddrs:          make(map[string]*NetAddress),
106                 addrLookup:        make(map[string]*knownAddress),
107                 bucketsNew:        make([]map[string]*knownAddress, newBucketCount),
108                 bucketsOld:        make([]map[string]*knownAddress, oldBucketCount),
109         }
110         for i := range a.bucketsNew {
111                 a.bucketsNew[i] = make(map[string]*knownAddress)
112         }
113         for i := range a.bucketsOld {
114                 a.bucketsOld[i] = make(map[string]*knownAddress)
115         }
116         a.BaseService = *cmn.NewBaseService(nil, "AddrBook", a)
117         return a
118 }
119
120 // OnStart implements Service.
121 func (a *AddrBook) OnStart() error {
122         if err := a.BaseService.OnStart(); err != nil {
123                 return err
124         }
125
126         a.loadFromFile()
127         go a.saveRoutine()
128         return nil
129 }
130
131 // OnStop implements Service.
132 func (a *AddrBook) OnStop() {
133         a.BaseService.OnStop()
134 }
135
136 // AddAddress add address to address book
137 func (a *AddrBook) AddAddress(addr, src *NetAddress) {
138         a.mtx.Lock()
139         defer a.mtx.Unlock()
140         log.WithFields(log.Fields{"addr": addr, "src": src}).Debug("add address to address book")
141         a.addAddress(addr, src)
142 }
143
144 func (a *AddrBook) NeedMoreAddrs() bool {
145         return a.Size() < needAddressThreshold
146 }
147
148 func (a *AddrBook) Size() int {
149         a.mtx.RLock()
150         defer a.mtx.RUnlock()
151         return a.size()
152 }
153
154 func (a *AddrBook) size() int {
155         return a.nNew + a.nOld
156 }
157
158 // PickAddress picks a random address from random bucket
159 func (a *AddrBook) PickAddress(bias int) *NetAddress {
160         a.mtx.RLock()
161         defer a.mtx.RUnlock()
162
163         if a.size() == 0 {
164                 return nil
165         }
166
167         // make sure bias is in the range [0, 100]
168         if bias > 100 {
169                 bias = 100
170         } else if bias < 0 {
171                 bias = 0
172         }
173
174         oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(bias))
175         newCorrelation := math.Sqrt(float64(a.nNew)) * float64(bias)
176         pickFromOldBucket := (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation
177         if (pickFromOldBucket && a.nOld == 0) || (!pickFromOldBucket && a.nNew == 0) {
178                 return nil
179         }
180
181         var bucket map[string]*knownAddress
182         for len(bucket) == 0 {
183                 if pickFromOldBucket {
184                         bucket = a.bucketsOld[a.rand.Intn(len(a.bucketsOld))]
185                 } else {
186                         bucket = a.bucketsNew[a.rand.Intn(len(a.bucketsNew))]
187                 }
188         }
189
190         randIndex := a.rand.Intn(len(bucket))
191         for _, ka := range bucket {
192                 if randIndex == 0 {
193                         return ka.Addr
194                 }
195                 randIndex--
196         }
197         return nil
198 }
199
200 // MarkGood marks the peer as good and moves it into an "old" bucket.
201 func (a *AddrBook) MarkGood(addr *NetAddress) {
202         a.mtx.Lock()
203         defer a.mtx.Unlock()
204
205         if ka := a.addrLookup[addr.String()]; ka != nil {
206                 ka.markGood()
207                 if ka.isNew() {
208                         a.moveToOld(ka)
209                 }
210         }
211 }
212
213 // MarkAttempt marks that an attempt was made to connect to the address.
214 func (a *AddrBook) MarkAttempt(addr *NetAddress) {
215         a.mtx.Lock()
216         defer a.mtx.Unlock()
217
218         if ka := a.addrLookup[addr.String()]; ka != nil {
219                 ka.markAttempt()
220         }
221 }
222
223 // RemoveAddress removes the address from the book.
224 func (a *AddrBook) RemoveAddress(addr *NetAddress) {
225         a.mtx.Lock()
226         defer a.mtx.Unlock()
227
228         if ka := a.addrLookup[addr.String()]; ka != nil {
229                 log.WithField("addr", addr).Debug("remove address from address book")
230                 a.removeFromAllBuckets(ka)
231         }
232 }
233
234 // GetSelection randomly selects some addresses (old & new). Suitable for peer-exchange protocols.
235 func (a *AddrBook) GetSelection() []*NetAddress {
236         a.mtx.RLock()
237         defer a.mtx.RUnlock()
238
239         bookSize := a.size()
240         if bookSize == 0 {
241                 return nil
242         }
243
244         numAddresses := cmn.MaxInt(cmn.MinInt(minGetSelection, bookSize), bookSize*getSelectionPercent/100)
245         numAddresses = cmn.MinInt(maxGetSelection, numAddresses)
246
247         allAddr := make([]*NetAddress, bookSize)
248         i := 0
249         for _, ka := range a.addrLookup {
250                 allAddr[i] = ka.Addr
251                 i++
252         }
253
254         // Fisher-Yates shuffle the array. We only need to do the first
255         // `numAddresses' since we are throwing the rest.
256         for i := 0; i < numAddresses; i++ {
257                 // pick a number between current index and the end
258                 j := rand.Intn(len(allAddr)-i) + i
259                 allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
260         }
261
262         // slice off the limit we are willing to share.
263         return allAddr[:numAddresses]
264 }
265
266 type addrBookJSON struct {
267         Key   string
268         Addrs []*knownAddress
269 }
270
271 func (a *AddrBook) Save() {
272         a.mtx.RLock()
273         defer a.mtx.RUnlock()
274
275         log.WithField("size", a.Size()).Debug("saving address book to file")
276         aJSON := &addrBookJSON{
277                 Key:   a.key,
278                 Addrs: []*knownAddress{},
279         }
280         for _, ka := range a.addrLookup {
281                 aJSON.Addrs = append(aJSON.Addrs, ka)
282         }
283
284         jsonBytes, err := json.MarshalIndent(aJSON, "", "\t")
285         if err != nil {
286                 log.WithField("err", err).Error("failed to save AddrBook to file")
287                 return
288         }
289
290         if err = cmn.WriteFileAtomic(a.filePath, jsonBytes, 0644); err != nil {
291                 log.WithFields(log.Fields{"file": a.filePath, "err": err}).Error("Failed to save AddrBook to file")
292         }
293 }
294
295 func (a *AddrBook) loadFromFile() bool {
296         if _, err := os.Stat(a.filePath); os.IsNotExist(err) {
297                 return false
298         }
299
300         r, err := os.Open(a.filePath)
301         if err != nil {
302                 cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", a.filePath, err))
303         }
304
305         defer r.Close()
306         aJSON := &addrBookJSON{}
307         if err = json.NewDecoder(r).Decode(aJSON); err != nil {
308                 cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", a.filePath, err))
309         }
310
311         a.key = aJSON.Key
312         for _, ka := range aJSON.Addrs {
313                 for _, bucketIndex := range ka.Buckets {
314                         bucket := a.getBucket(ka.BucketType, bucketIndex)
315                         bucket[ka.Addr.String()] = ka
316                 }
317                 a.addrLookup[ka.Addr.String()] = ka
318                 if ka.BucketType == bucketTypeNew {
319                         a.nNew++
320                 } else {
321                         a.nOld++
322                 }
323         }
324         return true
325 }
326
327 func (a *AddrBook) saveRoutine() {
328         ticker := time.NewTicker(dumpAddressInterval)
329         for {
330                 select {
331                 case <-ticker.C:
332                         a.Save()
333                 case <-a.Quit:
334                         a.Save()
335                         return
336                 }
337         }
338 }
339
340 func (a *AddrBook) getBucket(bucketType byte, bucketIdx int) map[string]*knownAddress {
341         switch bucketType {
342         case bucketTypeNew:
343                 return a.bucketsNew[bucketIdx]
344         case bucketTypeOld:
345                 return a.bucketsOld[bucketIdx]
346         default:
347                 cmn.PanicSanity("Should not happen")
348                 return nil
349         }
350 }
351
352 // Adds ka to new bucket. Returns false if it couldn't do it cuz buckets full.
353 // NOTE: currently it always returns true.
354 func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool {
355         if ka.isOld() {
356                 log.WithField("know address", ka).Error("cant add old address to new bucket")
357                 return false
358         }
359
360         addrStr := ka.Addr.String()
361         bucket := a.getBucket(bucketTypeNew, bucketIdx)
362         if _, ok := bucket[addrStr]; ok {
363                 return true
364         }
365
366         if len(bucket) > newBucketSize {
367                 log.Debug("addToNewBucket: new bucket is full, expiring new")
368                 a.expireNew(bucketIdx)
369         }
370
371         bucket[addrStr] = ka
372         a.addrLookup[addrStr] = ka
373         if ka.addBucketRef(bucketIdx) == 1 {
374                 a.nNew++
375         }
376         return true
377 }
378
379 // Adds ka to old bucket. Returns false if it couldn't do it cuz buckets full.
380 func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool {
381         if ka.isNew() {
382                 log.WithField("know address", ka).Error("cannot add old address to new bucket")
383                 return false
384         }
385         if len(ka.Buckets) != 0 {
386                 log.WithField("know address", ka).Error("cannot add already old address to another old bucket")
387                 return false
388         }
389
390         addrStr := ka.Addr.String()
391         bucket := a.getBucket(bucketTypeOld, bucketIdx)
392         if _, ok := bucket[addrStr]; ok {
393                 return true
394         }
395
396         if len(bucket) > oldBucketSize {
397                 return false
398         }
399
400         // Add to bucket.
401         bucket[addrStr] = ka
402         a.addrLookup[addrStr] = ka
403         if ka.addBucketRef(bucketIdx) == 1 {
404                 a.nOld++
405         }
406         return true
407 }
408
409 func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx int) {
410         if ka.BucketType != bucketType {
411                 log.WithField("know address", ka).Error("Bucket type mismatch")
412                 return
413         }
414
415         bucket := a.getBucket(bucketType, bucketIdx)
416         delete(bucket, ka.Addr.String())
417         if ka.removeBucketRef(bucketIdx) == 0 {
418                 if bucketType == bucketTypeNew {
419                         a.nNew--
420                 } else {
421                         a.nOld--
422                 }
423                 delete(a.addrLookup, ka.Addr.String())
424         }
425 }
426
427 func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
428         for _, bucketIdx := range ka.Buckets {
429                 bucket := a.getBucket(ka.BucketType, bucketIdx)
430                 delete(bucket, ka.Addr.String())
431         }
432         ka.Buckets = nil
433         if ka.BucketType == bucketTypeNew {
434                 a.nNew--
435         } else {
436                 a.nOld--
437         }
438         delete(a.addrLookup, ka.Addr.String())
439 }
440
441 func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress {
442         bucket := a.getBucket(bucketType, bucketIdx)
443         var oldest *knownAddress
444         for _, ka := range bucket {
445                 if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt) {
446                         oldest = ka
447                 }
448         }
449         return oldest
450 }
451
452 func (a *AddrBook) addAddress(addr, src *NetAddress) {
453         if _, ok := a.ourAddrs[addr.String()]; ok {
454                 return
455         }
456         if a.routabilityStrict && !addr.Routable() {
457                 log.WithField("address", addr).Error("cannot add non-routable address")
458                 return
459         }
460
461         ka := a.addrLookup[addr.String()]
462         if ka != nil {
463                 if ka.isOld() {
464                         return
465                 }
466                 if len(ka.Buckets) == maxNewBucketsPerAddress {
467                         return
468                 }
469                 if factor := int32(2 * len(ka.Buckets)); a.rand.Int31n(factor) != 0 {
470                         return
471                 }
472         } else {
473                 ka = newKnownAddress(addr, src)
474         }
475
476         bucket := a.calcNewBucket(addr, src)
477         a.addToNewBucket(ka, bucket)
478 }
479
480 // Make space in the new buckets by expiring the really bad entries.
481 // If no bad entries are available we remove the oldest.
482 func (a *AddrBook) expireNew(bucketIdx int) {
483         for addrStr, ka := range a.bucketsNew[bucketIdx] {
484                 if ka.isBad() {
485                         log.WithField("addr", addrStr).Info("expiring bad address")
486                         a.removeFromBucket(ka, bucketTypeNew, bucketIdx)
487                         return
488                 }
489         }
490
491         oldest := a.pickOldest(bucketTypeNew, bucketIdx)
492         a.removeFromBucket(oldest, bucketTypeNew, bucketIdx)
493 }
494
495 // Promotes an address from new to old.
496 // TODO: Move to old probabilistically.
497 // The better a node is, the less likely it should be evicted from an old bucket.
498 func (a *AddrBook) moveToOld(ka *knownAddress) {
499         if ka.isOld() {
500                 log.WithField("know address", ka).Error("cannot promote address that is already old")
501                 return
502         }
503         if len(ka.Buckets) == 0 {
504                 log.WithField("know address", ka).Error("cannot promote address that isn't in any new buckets")
505                 return
506         }
507
508         a.removeFromAllBuckets(ka)
509         ka.BucketType = bucketTypeOld
510
511         // Try to add it to its oldBucket destination.
512         oldBucketIdx := a.calcOldBucket(ka.Addr)
513         if ok := a.addToOldBucket(ka, oldBucketIdx); ok {
514                 return
515         }
516
517         // No room, must evict something
518         oldest := a.pickOldest(bucketTypeOld, oldBucketIdx)
519         a.removeFromBucket(oldest, bucketTypeOld, oldBucketIdx)
520         // Find new bucket to put oldest in
521         newBucketIdx := a.calcNewBucket(oldest.Addr, oldest.Src)
522         a.addToNewBucket(oldest, newBucketIdx)
523
524         if ok := a.addToOldBucket(ka, oldBucketIdx); !ok {
525                 log.WithField("know address", ka).Error("could not re-add to oldBucketIdx")
526         }
527 }
528
529 // doublesha256(  key + sourcegroup +
530 //                int64(doublesha256(key + group + sourcegroup))%bucket_per_group  ) % num_new_buckets
531 func (a *AddrBook) calcNewBucket(addr, src *NetAddress) int {
532         data1 := []byte{}
533         data1 = append(data1, []byte(a.key)...)
534         data1 = append(data1, []byte(a.groupKey(addr))...)
535         data1 = append(data1, []byte(a.groupKey(src))...)
536         hash1 := doubleSha256(data1)
537         hash64 := binary.BigEndian.Uint64(hash1)
538         hash64 %= newBucketsPerGroup
539         var hashbuf [8]byte
540         binary.BigEndian.PutUint64(hashbuf[:], hash64)
541         data2 := []byte{}
542         data2 = append(data2, []byte(a.key)...)
543         data2 = append(data2, a.groupKey(src)...)
544         data2 = append(data2, hashbuf[:]...)
545
546         hash2 := doubleSha256(data2)
547         return int(binary.BigEndian.Uint64(hash2) % newBucketCount)
548 }
549
550 // doublesha256(  key + group +
551 //                int64(doublesha256(key + addr))%buckets_per_group  ) % num_old_buckets
552 func (a *AddrBook) calcOldBucket(addr *NetAddress) int {
553         data1 := []byte{}
554         data1 = append(data1, []byte(a.key)...)
555         data1 = append(data1, []byte(addr.String())...)
556         hash1 := doubleSha256(data1)
557         hash64 := binary.BigEndian.Uint64(hash1)
558         hash64 %= oldBucketsPerGroup
559         var hashbuf [8]byte
560         binary.BigEndian.PutUint64(hashbuf[:], hash64)
561         data2 := []byte{}
562         data2 = append(data2, []byte(a.key)...)
563         data2 = append(data2, a.groupKey(addr)...)
564         data2 = append(data2, hashbuf[:]...)
565
566         hash2 := doubleSha256(data2)
567         return int(binary.BigEndian.Uint64(hash2) % oldBucketCount)
568 }
569
570 // Return a string representing the network group of this address.
571 // This is the /16 for IPv6, the /32 (/36 for he.net) for IPv6, the string
572 // "local" for a local address and the string "unroutable for an unroutable
573 // address.
574 func (a *AddrBook) groupKey(na *NetAddress) string {
575         if a.routabilityStrict && na.Local() {
576                 return "local"
577         }
578         if a.routabilityStrict && !na.Routable() {
579                 return "unroutable"
580         }
581
582         if ipv4 := na.IP.To4(); ipv4 != nil {
583                 return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String()
584         }
585         if na.RFC6145() || na.RFC6052() {
586                 // last four bytes are the ip address
587                 ip := net.IP(na.IP[12:16])
588                 return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
589         }
590
591         if na.RFC3964() {
592                 ip := net.IP(na.IP[2:7])
593                 return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
594
595         }
596         if na.RFC4380() {
597                 // teredo tunnels have the last 4 bytes as the v4 address XOR
598                 // 0xff.
599                 ip := net.IP(make([]byte, 4))
600                 for i, byte := range na.IP[12:16] {
601                         ip[i] = byte ^ 0xff
602                 }
603                 return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
604         }
605
606         // OK, so now we know ourselves to be a IPv6 address.
607         // bitcoind uses /32 for everything, except for Hurricane Electric's
608         // (he.net) IP range, which it uses /36 for.
609         bits := 32
610         heNet := &net.IPNet{IP: net.ParseIP("2001:470::"),
611                 Mask: net.CIDRMask(32, 128)}
612         if heNet.Contains(na.IP) {
613                 bits = 36
614         }
615
616         return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
617 }
618
619 //-----------------------------------------------------------------------------
620
621 /*
622    knownAddress
623
624    tracks information about a known network address that is used
625    to determine how viable an address is.
626 */
627 type knownAddress struct {
628         Addr        *NetAddress
629         Src         *NetAddress
630         Attempts    int32
631         LastAttempt time.Time
632         LastSuccess time.Time
633         BucketType  byte
634         Buckets     []int
635 }
636
637 func newKnownAddress(addr, src *NetAddress) *knownAddress {
638         return &knownAddress{
639                 Addr:        addr,
640                 Src:         src,
641                 Attempts:    0,
642                 LastAttempt: time.Now(),
643                 BucketType:  bucketTypeNew,
644                 Buckets:     nil,
645         }
646 }
647
648 func (ka *knownAddress) isOld() bool {
649         return ka.BucketType == bucketTypeOld
650 }
651
652 func (ka *knownAddress) isNew() bool {
653         return ka.BucketType == bucketTypeNew
654 }
655
656 func (ka *knownAddress) markAttempt() {
657         ka.LastAttempt = time.Now()
658         ka.Attempts += 1
659 }
660
661 func (ka *knownAddress) markGood() {
662         now := time.Now()
663         ka.LastAttempt = now
664         ka.LastSuccess = now
665         ka.Attempts = 0
666 }
667
668 func (ka *knownAddress) addBucketRef(bucketIdx int) int {
669         for _, bucket := range ka.Buckets {
670                 if bucket == bucketIdx {
671                         return -1
672                 }
673         }
674         ka.Buckets = append(ka.Buckets, bucketIdx)
675         return len(ka.Buckets)
676 }
677
678 func (ka *knownAddress) removeBucketRef(bucketIdx int) int {
679         buckets := []int{}
680         for _, bucket := range ka.Buckets {
681                 if bucket != bucketIdx {
682                         buckets = append(buckets, bucket)
683                 }
684         }
685         if len(buckets) != len(ka.Buckets)-1 {
686                 return -1
687         }
688         ka.Buckets = buckets
689         return len(ka.Buckets)
690 }
691
692 /*
693    An address is bad if the address in question has not been tried in the last
694    minute and meets one of the following criteria:
695
696    1) It claims to be from the future
697    2) It hasn't been seen in over a month
698    3) It has failed at least three times and never succeeded
699    4) It has failed ten times in the last week
700
701    All addresses that meet these criteria are assumed to be worthless and not
702    worth keeping hold of.
703 */
704 func (ka *knownAddress) isBad() bool {
705         if ka.BucketType == bucketTypeOld {
706                 return false
707         }
708         // Has been attempted in the last minute --> bad
709         if ka.LastAttempt.After(time.Now().Add(-1*time.Minute)) && ka.Attempts != 0 {
710                 return true
711         }
712
713         // Over a month old?
714         if ka.LastAttempt.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
715                 return true
716         }
717
718         // Never succeeded?
719         if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
720                 return true
721         }
722
723         // Hasn't succeeded in too long?
724         if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) && ka.Attempts >= maxFailures {
725                 return true
726         }
727
728         return false
729 }