github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/hostdb/scan.go (about)

     1  package hostdb
     2  
     3  // scan.go contains the functions which periodically scan the list of all hosts
     4  // to see which hosts are online or offline, and to get any updates to the
     5  // settings of the hosts.
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"math/big"
    11  	"time"
    12  
    13  	"github.com/NebulousLabs/Sia/build"
    14  	"github.com/NebulousLabs/Sia/crypto"
    15  	"github.com/NebulousLabs/Sia/encoding"
    16  	"github.com/NebulousLabs/Sia/modules"
    17  	"github.com/NebulousLabs/Sia/types"
    18  )
    19  
    20  const (
    21  	defaultScanSleep = 1*time.Hour + 37*time.Minute
    22  	maxScanSleep     = 4 * time.Hour
    23  	minScanSleep     = 1 * time.Hour
    24  
    25  	maxActiveHosts              = 500
    26  	inactiveHostCheckupQuantity = 250
    27  
    28  	maxSettingsLen = 2e3
    29  
    30  	hostRequestTimeout = 5 * time.Second
    31  
    32  	// scanningThreads is the number of threads that will be probing hosts for
    33  	// their settings and checking for reliability.
    34  	scanningThreads = 25
    35  )
    36  
    37  // Reliability is a measure of a host's uptime.
    38  var (
    39  	MaxReliability     = types.NewCurrency64(225) // Given the scanning defaults, about 3 weeks of survival.
    40  	DefaultReliability = types.NewCurrency64(75)  // Given the scanning defaults, about 1 week of survival.
    41  	UnreachablePenalty = types.NewCurrency64(1)
    42  )
    43  
    44  // addHostToScanPool creates a gofunc that adds a host to the scan pool. If the
    45  // scan pool is currently full, the blocking gofunc will not cause a deadlock.
    46  // The gofunc is created inside of this function to eliminate the burden of
    47  // needing to remember to call 'go addHostToScanPool'.
    48  func (hdb *HostDB) scanHostEntry(entry *hostEntry) {
    49  	go func() {
    50  		hdb.scanPool <- entry
    51  	}()
    52  }
    53  
    54  // decrementReliability reduces the reliability of a node, moving it out of the
    55  // set of active hosts or deleting it entirely if necessary.
    56  func (hdb *HostDB) decrementReliability(addr modules.NetAddress, penalty types.Currency) {
    57  	// Look up the entry and decrement the reliability.
    58  	entry, exists := hdb.allHosts[addr]
    59  	if !exists {
    60  		// TODO: should panic here
    61  		return
    62  	}
    63  	entry.Reliability = entry.Reliability.Sub(penalty)
    64  	entry.Online = false
    65  
    66  	// If the entry is in the active database, remove it from the active
    67  	// database.
    68  	node, exists := hdb.activeHosts[addr]
    69  	if exists {
    70  		delete(hdb.activeHosts, entry.NetAddress)
    71  		node.removeNode()
    72  	}
    73  
    74  	// If the reliability has fallen to 0, remove the host from the
    75  	// database entirely.
    76  	if entry.Reliability.IsZero() {
    77  		delete(hdb.allHosts, addr)
    78  	}
    79  }
    80  
    81  // threadedProbeHosts tries to fetch the settings of a host. If successful, the
    82  // host is put in the set of active hosts. If unsuccessful, the host id deleted
    83  // from the set of active hosts.
    84  func (hdb *HostDB) threadedProbeHosts() {
    85  	defer hdb.threadGroup.Done()
    86  	for hostEntry := range hdb.scanPool {
    87  		// Request settings from the queued host entry.
    88  		// TODO: use dialer.Cancel to shutdown quickly
    89  		hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey)
    90  		var settings modules.HostExternalSettings
    91  		err := func() error {
    92  			conn, err := hdb.dialer.DialTimeout(hostEntry.NetAddress, hostRequestTimeout)
    93  			if err != nil {
    94  				return err
    95  			}
    96  			defer conn.Close()
    97  			err = encoding.WriteObject(conn, modules.RPCSettings)
    98  			if err != nil {
    99  				return err
   100  			}
   101  			var pubkey crypto.PublicKey
   102  			copy(pubkey[:], hostEntry.PublicKey.Key)
   103  			return crypto.ReadSignedObject(conn, &settings, maxSettingsLen, pubkey)
   104  		}()
   105  		if err != nil {
   106  			hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "failed", err)
   107  		} else {
   108  			hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "succeeded")
   109  		}
   110  
   111  		// Now that network communication is done, lock the hostdb to modify the
   112  		// host entry.
   113  		func() {
   114  			hdb.mu.Lock()
   115  			defer hdb.mu.Unlock()
   116  
   117  			// Regardless of whether the host responded, add it to allHosts.
   118  			priorHost, exists := hdb.allHosts[hostEntry.NetAddress]
   119  			if !exists {
   120  				hdb.allHosts[hostEntry.NetAddress] = hostEntry
   121  			}
   122  
   123  			// If the scan was unsuccessful, decrement the host's reliability.
   124  			if err != nil {
   125  				if exists && bytes.Equal(priorHost.PublicKey.Key, hostEntry.PublicKey.Key) {
   126  					// Only decrement the reliability if the public key in the
   127  					// hostdb matches the public key in the host announcement -
   128  					// the failure may just be a failed signature, indicating
   129  					// the wrong public key.
   130  					hdb.decrementReliability(hostEntry.NetAddress, UnreachablePenalty)
   131  				}
   132  				return
   133  			}
   134  
   135  			// Update the host settings, reliability, and weight. The old NetAddress
   136  			// must be preserved.
   137  			settings.NetAddress = hostEntry.HostExternalSettings.NetAddress
   138  			hostEntry.HostExternalSettings = settings
   139  			hostEntry.Reliability = MaxReliability
   140  			hostEntry.Weight = calculateHostWeight(*hostEntry)
   141  			hostEntry.Online = true
   142  
   143  			// If 'maxActiveHosts' has not been reached, add the host to the
   144  			// activeHosts tree.
   145  			if _, exists := hdb.activeHosts[hostEntry.NetAddress]; !exists && len(hdb.activeHosts) < maxActiveHosts {
   146  				hdb.insertNode(hostEntry)
   147  			}
   148  			hdb.save()
   149  		}()
   150  	}
   151  }
   152  
   153  // threadedScan is an ongoing function which will query the full set of hosts
   154  // every few hours to see who is online and available for uploading.
   155  func (hdb *HostDB) threadedScan() {
   156  	defer hdb.threadGroup.Done()
   157  	for {
   158  		// Determine who to scan. At most 'maxActiveHosts' will be scanned,
   159  		// starting with the active hosts followed by a random selection of the
   160  		// inactive hosts.
   161  		func() {
   162  			hdb.mu.Lock()
   163  			defer hdb.mu.Unlock()
   164  
   165  			// Scan all active hosts.
   166  			for _, host := range hdb.activeHosts {
   167  				hdb.scanHostEntry(host.hostEntry)
   168  			}
   169  
   170  			// Assemble all of the inactive hosts into a single array.
   171  			var entries []*hostEntry
   172  			for _, entry := range hdb.allHosts {
   173  				_, exists := hdb.activeHosts[entry.NetAddress]
   174  				if !exists {
   175  					entries = append(entries, entry)
   176  				}
   177  			}
   178  
   179  			// Generate a random ordering of up to inactiveHostCheckupQuantity
   180  			// hosts.
   181  			hostOrder, err := crypto.Perm(len(entries))
   182  			if err != nil {
   183  				hdb.log.Println("ERR: could not generate random permutation:", err)
   184  			}
   185  
   186  			// Scan each host.
   187  			for i := 0; i < len(hostOrder) && i < inactiveHostCheckupQuantity; i++ {
   188  				hdb.scanHostEntry(entries[hostOrder[i]])
   189  			}
   190  		}()
   191  
   192  		// Sleep for a random amount of time before doing another round of
   193  		// scanning. The minimums and maximums keep the scan time reasonable,
   194  		// while the randomness prevents the scanning from always happening at
   195  		// the same time of day or week.
   196  		maxBig := big.NewInt(int64(maxScanSleep))
   197  		minBig := big.NewInt(int64(minScanSleep))
   198  		randSleep, err := rand.Int(rand.Reader, maxBig.Sub(maxBig, minBig))
   199  		if err != nil {
   200  			build.Critical(err)
   201  			// If there's an error, sleep for the default amount of time.
   202  			defaultBig := big.NewInt(int64(defaultScanSleep))
   203  			randSleep = defaultBig.Sub(defaultBig, minBig)
   204  		}
   205  
   206  		select {
   207  		// awaken and exit if hostdb is closing
   208  		case <-hdb.closeChan:
   209  			return
   210  		case <-time.After(time.Duration(randSleep.Int64()) + minScanSleep):
   211  		}
   212  	}
   213  }