You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

701 lines
16 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. // Modified for Tendermint
  2. // Originally Copyright (c) 2013-2014 Conformal Systems LLC.
  3. // https://github.com/conformal/btcd/blob/master/LICENSE
  4. package peer
  5. import (
  6. crand "crypto/rand" // for seeding
  7. "encoding/binary"
  8. "encoding/json"
  9. "fmt"
  10. "io"
  11. "math"
  12. "math/rand"
  13. "net"
  14. "os"
  15. "sync"
  16. "sync/atomic"
  17. "time"
  18. . "github.com/tendermint/tendermint/binary"
  19. )
  20. /* AddrBook - concurrency safe peer address manager */
  21. type AddrBook struct {
  22. filePath string
  23. mtx sync.Mutex
  24. rand *rand.Rand
  25. key [32]byte
  26. addrIndex map[string]*knownAddress // new & old
  27. addrNew [newBucketCount]map[string]*knownAddress
  28. addrOld [oldBucketCount][]*knownAddress
  29. started int32
  30. shutdown int32
  31. wg sync.WaitGroup
  32. quit chan struct{}
  33. nOld int
  34. nNew int
  35. }
  36. const (
  37. // addresses under which the address manager will claim to need more addresses.
  38. needAddressThreshold = 1000
  39. // interval used to dump the address cache to disk for future use.
  40. dumpAddressInterval = time.Minute * 2
  41. // max addresses in each old address bucket.
  42. oldBucketSize = 64
  43. // buckets we split old addresses over.
  44. oldBucketCount = 64
  45. // max addresses in each new address bucket.
  46. newBucketSize = 64
  47. // buckets that we spread new addresses over.
  48. newBucketCount = 256
  49. // old buckets over which an address group will be spread.
  50. oldBucketsPerGroup = 4
  51. // new buckets over which an source address group will be spread.
  52. newBucketsPerGroup = 32
  53. // buckets a frequently seen new address may end up in.
  54. newBucketsPerAddress = 4
  55. // days before which we assume an address has vanished
  56. // if we have not seen it announced in that long.
  57. numMissingDays = 30
  58. // tries without a single success before we assume an address is bad.
  59. numRetries = 3
  60. // max failures we will accept without a success before considering an address bad.
  61. maxFailures = 10
  62. // days since the last success before we will consider evicting an address.
  63. minBadDays = 7
  64. // max addresses that we will send in response to a GetSelection
  65. getSelectionMax = 2500
  66. // % of total addresses known that we will share with a call to GetSelection
  67. getSelectionPercent = 23
  68. // current version of the on-disk format.
  69. serializationVersion = 1
  70. )
  71. // Use Start to begin processing asynchronous address updates.
  72. func NewAddrBook(filePath string) *AddrBook {
  73. am := AddrBook{
  74. rand: rand.New(rand.NewSource(time.Now().UnixNano())),
  75. quit: make(chan struct{}),
  76. filePath: filePath,
  77. }
  78. am.init()
  79. return &am
  80. }
  81. // When modifying this, don't forget to update loadFromFile()
  82. func (a *AddrBook) init() {
  83. a.addrIndex = make(map[string]*knownAddress)
  84. io.ReadFull(crand.Reader, a.key[:])
  85. for i := range a.addrNew {
  86. a.addrNew[i] = make(map[string]*knownAddress)
  87. }
  88. for i := range a.addrOld {
  89. a.addrOld[i] = make([]*knownAddress, 0, oldBucketSize)
  90. }
  91. }
  92. func (a *AddrBook) Start() {
  93. if atomic.AddInt32(&a.started, 1) != 1 {
  94. return
  95. }
  96. log.Trace("Starting address manager")
  97. a.loadFromFile(a.filePath)
  98. a.wg.Add(1)
  99. go a.addressHandler()
  100. }
  101. func (a *AddrBook) Stop() {
  102. if atomic.AddInt32(&a.shutdown, 1) != 1 {
  103. return
  104. }
  105. log.Infof("Address manager shutting down")
  106. close(a.quit)
  107. a.wg.Wait()
  108. }
  109. func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
  110. a.mtx.Lock()
  111. defer a.mtx.Unlock()
  112. a.addAddress(addr, src)
  113. }
  114. func (a *AddrBook) NeedMoreAddresses() bool {
  115. return a.Size() < needAddressThreshold
  116. }
  117. func (a *AddrBook) Size() int {
  118. a.mtx.Lock()
  119. defer a.mtx.Unlock()
  120. return a.size()
  121. }
  122. func (a *AddrBook) size() int {
  123. return a.nNew + a.nOld
  124. }
  125. // Pick an address to connect to with new/old bias.
  126. func (a *AddrBook) PickAddress(newBias int) *knownAddress {
  127. a.mtx.Lock()
  128. defer a.mtx.Unlock()
  129. if a.nOld == 0 && a.nNew == 0 {
  130. return nil
  131. }
  132. if newBias > 100 {
  133. newBias = 100
  134. }
  135. if newBias < 0 {
  136. newBias = 0
  137. }
  138. // Bias between new and old addresses.
  139. oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(newBias))
  140. newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias)
  141. if (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation {
  142. // pick random Old bucket.
  143. var bucket []*knownAddress = nil
  144. for len(bucket) == 0 {
  145. bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]
  146. }
  147. // pick a random ka from bucket.
  148. return bucket[a.rand.Intn(len(bucket))]
  149. } else {
  150. // pick random New bucket.
  151. var bucket map[string]*knownAddress = nil
  152. for len(bucket) == 0 {
  153. bucket = a.addrNew[a.rand.Intn(len(a.addrNew))]
  154. }
  155. // pick a random ka from bucket.
  156. randIndex := a.rand.Intn(len(bucket))
  157. for _, ka := range bucket {
  158. randIndex--
  159. if randIndex == 0 {
  160. return ka
  161. }
  162. }
  163. panic("Should not happen")
  164. }
  165. return nil
  166. }
  167. func (a *AddrBook) MarkGood(addr *NetAddress) {
  168. a.mtx.Lock()
  169. defer a.mtx.Unlock()
  170. ka := a.addrIndex[addr.String()]
  171. if ka == nil {
  172. return
  173. }
  174. ka.MarkGood()
  175. if ka.OldBucket == -1 {
  176. a.moveToOld(ka)
  177. }
  178. }
  179. func (a *AddrBook) MarkAttempt(addr *NetAddress) {
  180. a.mtx.Lock()
  181. defer a.mtx.Unlock()
  182. ka := a.addrIndex[addr.String()]
  183. if ka == nil {
  184. return
  185. }
  186. ka.MarkAttempt()
  187. }
  188. /* Peer exchange */
  189. // GetSelection randomly selects some addresses (old & new). Suitable for peer-exchange protocols.
  190. func (a *AddrBook) GetSelection() []*NetAddress {
  191. a.mtx.Lock()
  192. defer a.mtx.Unlock()
  193. if a.size() == 0 {
  194. return nil
  195. }
  196. allAddr := make([]*NetAddress, a.size())
  197. i := 0
  198. for _, v := range a.addrIndex {
  199. allAddr[i] = v.Addr
  200. i++
  201. }
  202. numAddresses := len(allAddr) * getSelectionPercent / 100
  203. if numAddresses > getSelectionMax {
  204. numAddresses = getSelectionMax
  205. }
  206. // Fisher-Yates shuffle the array. We only need to do the first
  207. // `numAddresses' since we are throwing the rest.
  208. for i := 0; i < numAddresses; i++ {
  209. // pick a number between current index and the end
  210. j := rand.Intn(len(allAddr)-i) + i
  211. allAddr[i], allAddr[j] = allAddr[j], allAddr[i]
  212. }
  213. // slice off the limit we are willing to share.
  214. return allAddr[:numAddresses]
  215. }
  216. /* Loading & Saving */
  217. type addrBookJSON struct {
  218. Key [32]byte
  219. AddrNew [newBucketCount][]*knownAddress
  220. AddrOld [oldBucketCount][]*knownAddress
  221. NumOld int
  222. NumNew int
  223. }
  224. func (a *AddrBook) saveToFile(filePath string) {
  225. // turn a.addrNew into an array like a.addrOld
  226. __addrNew := [newBucketCount][]*knownAddress{}
  227. for i, newBucket := range a.addrNew {
  228. var array []*knownAddress = make([]*knownAddress, 0)
  229. for _, ka := range newBucket {
  230. array = append(array, ka)
  231. }
  232. __addrNew[i] = array
  233. }
  234. aJSON := &addrBookJSON{
  235. Key: a.key,
  236. AddrNew: __addrNew,
  237. AddrOld: a.addrOld,
  238. NumOld: a.nOld,
  239. NumNew: a.nNew,
  240. }
  241. w, err := os.Create(filePath)
  242. if err != nil {
  243. log.Error("Error opening file: ", filePath, err)
  244. return
  245. }
  246. enc := json.NewEncoder(w)
  247. defer w.Close()
  248. err = enc.Encode(&aJSON)
  249. if err != nil {
  250. panic(err)
  251. }
  252. }
  253. func (a *AddrBook) loadFromFile(filePath string) {
  254. // If doesn't exist, do nothing.
  255. _, err := os.Stat(filePath)
  256. if os.IsNotExist(err) {
  257. return
  258. }
  259. // Load addrBookJSON{}
  260. r, err := os.Open(filePath)
  261. if err != nil {
  262. panic(fmt.Errorf("%s error opening file: %v", filePath, err))
  263. }
  264. defer r.Close()
  265. aJSON := &addrBookJSON{}
  266. dec := json.NewDecoder(r)
  267. err = dec.Decode(aJSON)
  268. if err != nil {
  269. panic(fmt.Errorf("error reading %s: %v", filePath, err))
  270. }
  271. // Now we need to restore the fields
  272. // Restore the key
  273. copy(a.key[:], aJSON.Key[:])
  274. // Restore .addrNew
  275. for i, newBucket := range aJSON.AddrNew {
  276. for _, ka := range newBucket {
  277. a.addrNew[i][ka.Addr.String()] = ka
  278. a.addrIndex[ka.Addr.String()] = ka
  279. }
  280. }
  281. // Restore .addrOld
  282. for i, oldBucket := range aJSON.AddrOld {
  283. copy(a.addrOld[i], oldBucket)
  284. for _, ka := range oldBucket {
  285. a.addrIndex[ka.Addr.String()] = ka
  286. }
  287. }
  288. // Restore simple fields
  289. a.nNew = aJSON.NumNew
  290. a.nOld = aJSON.NumOld
  291. }
  292. /* Private methods */
  293. func (a *AddrBook) addressHandler() {
  294. dumpAddressTicker := time.NewTicker(dumpAddressInterval)
  295. out:
  296. for {
  297. select {
  298. case <-dumpAddressTicker.C:
  299. a.saveToFile(a.filePath)
  300. case <-a.quit:
  301. break out
  302. }
  303. }
  304. dumpAddressTicker.Stop()
  305. a.saveToFile(a.filePath)
  306. a.wg.Done()
  307. log.Trace("Address handler done")
  308. }
  309. func (a *AddrBook) addAddress(addr, src *NetAddress) {
  310. if !addr.Routable() {
  311. return
  312. }
  313. key := addr.String()
  314. ka := a.addrIndex[key]
  315. if ka != nil {
  316. // Already added
  317. if ka.OldBucket != -1 {
  318. return
  319. }
  320. if ka.NewRefs == newBucketsPerAddress {
  321. return
  322. }
  323. // The more entries we have, the less likely we are to add more.
  324. factor := int32(2 * ka.NewRefs)
  325. if a.rand.Int31n(factor) != 0 {
  326. return
  327. }
  328. } else {
  329. ka = NewknownAddress(addr, src)
  330. a.addrIndex[key] = ka
  331. a.nNew++
  332. }
  333. bucket := a.getNewBucket(addr, src)
  334. // Already exists?
  335. if _, ok := a.addrNew[bucket][key]; ok {
  336. return
  337. }
  338. // Enforce max addresses.
  339. if len(a.addrNew[bucket]) > newBucketSize {
  340. log.Tracef("new bucket is full, expiring old ")
  341. a.expireNew(bucket)
  342. }
  343. // Add to new bucket.
  344. ka.NewRefs++
  345. a.addrNew[bucket][key] = ka
  346. log.Tracef("Added new address %s for a total of %d addresses", addr, a.nOld+a.nNew)
  347. }
  348. // Make space in the new buckets by expiring the really bad entries.
  349. // If no bad entries are available we look at a few and remove the oldest.
  350. func (a *AddrBook) expireNew(bucket int) {
  351. var oldest *knownAddress
  352. for k, v := range a.addrNew[bucket] {
  353. // If an entry is bad, throw it away
  354. if v.IsBad() {
  355. log.Tracef("expiring bad address %v", k)
  356. delete(a.addrNew[bucket], k)
  357. v.NewRefs--
  358. if v.NewRefs == 0 {
  359. a.nNew--
  360. delete(a.addrIndex, k)
  361. }
  362. return
  363. }
  364. // or, keep track of the oldest entry
  365. if oldest == nil {
  366. oldest = v
  367. } else if v.LastAttempt.Before(oldest.LastAttempt.Time) {
  368. oldest = v
  369. }
  370. }
  371. // If we haven't thrown out a bad entry, throw out the oldest entry
  372. if oldest != nil {
  373. key := oldest.Addr.String()
  374. log.Tracef("expiring oldest address %v", key)
  375. delete(a.addrNew[bucket], key)
  376. oldest.NewRefs--
  377. if oldest.NewRefs == 0 {
  378. a.nNew--
  379. delete(a.addrIndex, key)
  380. }
  381. }
  382. }
  383. func (a *AddrBook) moveToOld(ka *knownAddress) {
  384. // Remove from all new buckets.
  385. // Remember one of those new buckets.
  386. addrKey := ka.Addr.String()
  387. freedBucket := -1
  388. for i := range a.addrNew {
  389. // we check for existance so we can record the first one
  390. if _, ok := a.addrNew[i][addrKey]; ok {
  391. delete(a.addrNew[i], addrKey)
  392. ka.NewRefs--
  393. if freedBucket == -1 {
  394. freedBucket = i
  395. }
  396. }
  397. }
  398. a.nNew--
  399. if freedBucket == -1 {
  400. panic("Expected to find addr in at least one new bucket")
  401. }
  402. oldBucket := a.getOldBucket(ka.Addr)
  403. // If room in oldBucket, put it in.
  404. if len(a.addrOld[oldBucket]) < oldBucketSize {
  405. ka.OldBucket = Int16(oldBucket)
  406. a.addrOld[oldBucket] = append(a.addrOld[oldBucket], ka)
  407. a.nOld++
  408. return
  409. }
  410. // No room, we have to evict something else.
  411. rmkaIndex := a.pickOld(oldBucket)
  412. rmka := a.addrOld[oldBucket][rmkaIndex]
  413. // Find a new bucket to put rmka in.
  414. newBucket := a.getNewBucket(rmka.Addr, rmka.Src)
  415. if len(a.addrNew[newBucket]) >= newBucketSize {
  416. newBucket = freedBucket
  417. }
  418. // Replace with ka in list.
  419. ka.OldBucket = Int16(oldBucket)
  420. a.addrOld[oldBucket][rmkaIndex] = ka
  421. rmka.OldBucket = -1
  422. // Put rmka into new bucket
  423. rmkey := rmka.Addr.String()
  424. log.Tracef("Replacing %s with %s in old", rmkey, addrKey)
  425. a.addrNew[newBucket][rmkey] = rmka
  426. rmka.NewRefs++
  427. a.nNew++
  428. }
  429. // Returns the index in old bucket of oldest entry.
  430. func (a *AddrBook) pickOld(bucket int) int {
  431. var oldest *knownAddress
  432. var oldestIndex int
  433. for i, ka := range a.addrOld[bucket] {
  434. if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt.Time) {
  435. oldest = ka
  436. oldestIndex = i
  437. }
  438. }
  439. return oldestIndex
  440. }
  441. // doublesha256(key + sourcegroup +
  442. // int64(doublesha256(key + group + sourcegroup))%bucket_per_source_group) % num_new_buckes
  443. func (a *AddrBook) getNewBucket(addr, src *NetAddress) int {
  444. data1 := []byte{}
  445. data1 = append(data1, a.key[:]...)
  446. data1 = append(data1, []byte(GroupKey(addr))...)
  447. data1 = append(data1, []byte(GroupKey(src))...)
  448. hash1 := DoubleSha256(data1)
  449. hash64 := binary.LittleEndian.Uint64(hash1)
  450. hash64 %= newBucketsPerGroup
  451. var hashbuf [8]byte
  452. binary.LittleEndian.PutUint64(hashbuf[:], hash64)
  453. data2 := []byte{}
  454. data2 = append(data2, a.key[:]...)
  455. data2 = append(data2, GroupKey(src)...)
  456. data2 = append(data2, hashbuf[:]...)
  457. hash2 := DoubleSha256(data2)
  458. return int(binary.LittleEndian.Uint64(hash2) % newBucketCount)
  459. }
  460. // doublesha256(key + group + truncate_to_64bits(doublesha256(key + addr))%buckets_per_group) % num_buckets
  461. func (a *AddrBook) getOldBucket(addr *NetAddress) int {
  462. data1 := []byte{}
  463. data1 = append(data1, a.key[:]...)
  464. data1 = append(data1, []byte(addr.String())...)
  465. hash1 := DoubleSha256(data1)
  466. hash64 := binary.LittleEndian.Uint64(hash1)
  467. hash64 %= oldBucketsPerGroup
  468. var hashbuf [8]byte
  469. binary.LittleEndian.PutUint64(hashbuf[:], hash64)
  470. data2 := []byte{}
  471. data2 = append(data2, a.key[:]...)
  472. data2 = append(data2, GroupKey(addr)...)
  473. data2 = append(data2, hashbuf[:]...)
  474. hash2 := DoubleSha256(data2)
  475. return int(binary.LittleEndian.Uint64(hash2) % oldBucketCount)
  476. }
  477. // Return a string representing the network group of this address.
  478. // This is the /16 for IPv6, the /32 (/36 for he.net) for IPv6, the string
  479. // "local" for a local address and the string "unroutable for an unroutable
  480. // address.
  481. func GroupKey(na *NetAddress) string {
  482. if na.Local() {
  483. return "local"
  484. }
  485. if !na.Routable() {
  486. return "unroutable"
  487. }
  488. if ipv4 := na.IP.To4(); ipv4 != nil {
  489. return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String()
  490. }
  491. if na.RFC6145() || na.RFC6052() {
  492. // last four bytes are the ip address
  493. ip := net.IP(na.IP[12:16])
  494. return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
  495. }
  496. if na.RFC3964() {
  497. ip := net.IP(na.IP[2:7])
  498. return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
  499. }
  500. if na.RFC4380() {
  501. // teredo tunnels have the last 4 bytes as the v4 address XOR
  502. // 0xff.
  503. ip := net.IP(make([]byte, 4))
  504. for i, byte := range na.IP[12:16] {
  505. ip[i] = byte ^ 0xff
  506. }
  507. return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String()
  508. }
  509. // OK, so now we know ourselves to be a IPv6 address.
  510. // bitcoind uses /32 for everything, except for Hurricane Electric's
  511. // (he.net) IP range, which it uses /36 for.
  512. bits := 32
  513. heNet := &net.IPNet{IP: net.ParseIP("2001:470::"),
  514. Mask: net.CIDRMask(32, 128)}
  515. if heNet.Contains(na.IP) {
  516. bits = 36
  517. }
  518. return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
  519. }
  520. /*
  521. knownAddress
  522. tracks information about a known network address that is used
  523. to determine how viable an address is.
  524. */
  525. type knownAddress struct {
  526. Addr *NetAddress
  527. Src *NetAddress
  528. Attempts UInt32
  529. LastAttempt Time
  530. LastSuccess Time
  531. NewRefs UInt16
  532. OldBucket Int16
  533. }
  534. func NewknownAddress(addr *NetAddress, src *NetAddress) *knownAddress {
  535. return &knownAddress{
  536. Addr: addr,
  537. Src: src,
  538. OldBucket: -1,
  539. LastAttempt: Time{time.Now()},
  540. Attempts: 0,
  541. }
  542. }
  543. func ReadknownAddress(r io.Reader) *knownAddress {
  544. return &knownAddress{
  545. Addr: ReadNetAddress(r),
  546. Src: ReadNetAddress(r),
  547. Attempts: ReadUInt32(r),
  548. LastAttempt: ReadTime(r),
  549. LastSuccess: ReadTime(r),
  550. NewRefs: ReadUInt16(r),
  551. OldBucket: ReadInt16(r),
  552. }
  553. }
  554. func (ka *knownAddress) WriteTo(w io.Writer) (n int64, err error) {
  555. n, err = WriteOnto(ka.Addr, w, n, err)
  556. n, err = WriteOnto(ka.Src, w, n, err)
  557. n, err = WriteOnto(ka.Attempts, w, n, err)
  558. n, err = WriteOnto(ka.LastAttempt, w, n, err)
  559. n, err = WriteOnto(ka.LastSuccess, w, n, err)
  560. n, err = WriteOnto(ka.NewRefs, w, n, err)
  561. n, err = WriteOnto(ka.OldBucket, w, n, err)
  562. return
  563. }
  564. func (ka *knownAddress) MarkAttempt() {
  565. now := Time{time.Now()}
  566. ka.LastAttempt = now
  567. ka.Attempts += 1
  568. }
  569. func (ka *knownAddress) MarkGood() {
  570. now := Time{time.Now()}
  571. ka.LastAttempt = now
  572. ka.Attempts = 0
  573. ka.LastSuccess = now
  574. }
  575. /*
  576. An address is bad if the address in question has not been tried in the last
  577. minute and meets one of the following criteria:
  578. 1) It claims to be from the future
  579. 2) It hasn't been seen in over a month
  580. 3) It has failed at least three times and never succeeded
  581. 4) It has failed ten times in the last week
  582. All addresses that meet these criteria are assumed to be worthless and not
  583. worth keeping hold of.
  584. */
  585. func (ka *knownAddress) IsBad() bool {
  586. // Has been attempted in the last minute --> good
  587. if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) {
  588. return false
  589. }
  590. // Over a month old?
  591. if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
  592. return true
  593. }
  594. // Never succeeded?
  595. if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
  596. return true
  597. }
  598. // Hasn't succeeded in too long?
  599. if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
  600. ka.Attempts >= maxFailures {
  601. return true
  602. }
  603. return false
  604. }