diff --git a/dnsseed/setup.go b/dnsseed/setup.go index 808eeec..ad3b3fc 100644 --- a/dnsseed/setup.go +++ b/dnsseed/setup.go @@ -1,6 +1,7 @@ package dnsseed import ( + crypto_rand "crypto/rand" "net" "net/url" "time" @@ -73,10 +74,18 @@ func setup(c *caddy.Controller) error { // Start the update timer go func() { log.Infof("Starting update timer. Will crawl every %.0f minutes.", updateInterval.Minutes()) + randByte := []byte{0} for { select { case <-time.After(updateInterval): runCrawl(seeder) + crypto_rand.Read(randByte[:]) + if randByte[0] >= byte(192) { + // About 25% of the time, retry the blacklist. + // This stops us from losing peers forever due to + // temporary downtime. + seeder.RetryBlacklist() + } } } }() diff --git a/zcash/address_book.go b/zcash/address_book.go index 861dca2..7196f5c 100644 --- a/zcash/address_book.go +++ b/zcash/address_book.go @@ -104,6 +104,7 @@ func (bk *AddressBook) Remove(s PeerKey) { } } +// Blacklist adds an address to the blacklist so we won't try to connect to it again. func (bk *AddressBook) Blacklist(s PeerKey) { bk.addrState.Lock() defer bk.addrState.Unlock() @@ -122,6 +123,16 @@ func (bk *AddressBook) Blacklist(s PeerKey) { } } +// Redeem removes an address from the blacklist. +func (bk *AddressBook) Redeem(s PeerKey) { + bk.addrState.Lock() + defer bk.addrState.Unlock() + + if _, ok := bk.blacklist[s]; ok { + delete(bk.blacklist, s) + } +} + // Touch updates the last-seen timestamp if the peer is in the valid address book or does nothing if not. func (bk *AddressBook) Touch(s PeerKey) { bk.addrState.Lock() diff --git a/zcash/client.go b/zcash/client.go index 6c10e0c..3c6c410 100644 --- a/zcash/client.go +++ b/zcash/client.go @@ -426,6 +426,48 @@ func (s *Seeder) RefreshAddresses(disconnect bool) { s.logger.Printf("RefreshAddresses() finished.") } +// RetryBlacklist checks if the addresses in our blacklist are usable again. +// If the trial connection succeeds, they're removed from the blacklist. +func (s *Seeder) RetryBlacklist() { + s.logger.Printf("Giving the blacklist another chance") + + var blacklistQueue chan *Address + var wg sync.WaitGroup + + // XXX lil awkward to allocate a channel whose size we can't determine without a lock here + s.addrBook.enqueueAddrs(&blacklistQueue) + + for i := 0; i < crawlerGoroutineCount; i++ { + wg.Add(1) + go func() { + for len(blacklistQueue) > 0 { + // Pull the next address off the queue + next := <-blacklistQueue + na := next.netaddr + + ipString := na.IP.String() + portString := strconv.Itoa(int(na.Port)) + + err := s.Connect(ipString, portString) + + if err != nil { + // Connection failed. Peer remains blacklisted. + continue + } + + s.DisconnectPeer(next.asPeerKey()) + + // This would deadlock if enqueueAddrs still held the RLock. + s.addrBook.Redeem(next.asPeerKey()) + } + wg.Done() + }() + } + + wg.Wait() + s.logger.Printf("RetryBlacklist() finished.") +} + // WaitForAddresses waits for n addresses to be confirmed and available in the address book. func (s *Seeder) WaitForAddresses(n int, timeout time.Duration) error { done := make(chan struct{}) @@ -462,3 +504,8 @@ func (s *Seeder) GetPeerCount() int { func (s *Seeder) testBlacklist(pk PeerKey) { s.addrBook.Blacklist(pk) } + +// testRedeen adds a peer to the blacklist directly, for testing. +func (s *Seeder) testRedeem(pk PeerKey) { + s.addrBook.Redeem(pk) +} diff --git a/zcash/client_test.go b/zcash/client_test.go index 728109c..c4ddfe1 100644 --- a/zcash/client_test.go +++ b/zcash/client_test.go @@ -231,4 +231,10 @@ func TestBlacklist(t *testing.T) { if err != ErrBlacklistedPeer { t.Errorf("Blacklist did not prevent connection") } + + regSeeder.testRedeem(PeerKey("127.0.0.1:12345")) + err = regSeeder.Connect("127.0.0.1", "12345") + if err != nil { + t.Errorf("Redeem didn't allow reconnecting") + } }