github.com/NebulousLabs/Sia@v1.3.7/modules/renter/hostdb/hostdb.go (about) 1 // Package hostdb provides a HostDB object that implements the renter.hostDB 2 // interface. The blockchain is scanned for host announcements and hosts that 3 // are found get added to the host database. The database continually scans the 4 // set of hosts it has found and updates who is online. 5 package hostdb 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "sync" 13 "time" 14 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/modules/renter/hostdb/hosttree" 17 "github.com/NebulousLabs/Sia/persist" 18 "github.com/NebulousLabs/Sia/types" 19 "github.com/NebulousLabs/threadgroup" 20 ) 21 22 var ( 23 // ErrInitialScanIncomplete is returned whenever an operation is not 24 // allowed to be executed before the initial host scan has finished. 25 ErrInitialScanIncomplete = errors.New("initial hostdb scan is not yet completed") 26 errNilCS = errors.New("cannot create hostdb with nil consensus set") 27 errNilGateway = errors.New("cannot create hostdb with nil gateway") 28 ) 29 30 // The HostDB is a database of potential hosts. It assigns a weight to each 31 // host based on their hosting parameters, and then can select hosts at random 32 // for uploading files. 33 type HostDB struct { 34 // dependencies 35 cs modules.ConsensusSet 36 deps modules.Dependencies 37 gateway modules.Gateway 38 log *persist.Logger 39 mu sync.RWMutex 40 persistDir string 41 tg threadgroup.ThreadGroup 42 43 // The hostTree is the root node of the tree that organizes hosts by 44 // weight. The tree is necessary for selecting weighted hosts at 45 // random. 46 hostTree *hosttree.HostTree 47 48 // the scanPool is a set of hosts that need to be scanned. There are a 49 // handful of goroutines constantly waiting on the channel for hosts to 50 // scan. The scan map is used to prevent duplicates from entering the scan 51 // pool. 52 initialScanComplete bool 53 initialScanLatencies []time.Duration 54 scanList []modules.HostDBEntry 55 scanMap map[string]struct{} 56 scanWait bool 57 scanningThreads int 58 59 blockHeight types.BlockHeight 60 lastChange modules.ConsensusChangeID 61 } 62 63 // New returns a new HostDB. 64 func New(g modules.Gateway, cs modules.ConsensusSet, persistDir string) (*HostDB, error) { 65 // Check for nil inputs. 66 if g == nil { 67 return nil, errNilGateway 68 } 69 if cs == nil { 70 return nil, errNilCS 71 } 72 // Create HostDB using production dependencies. 73 return NewCustomHostDB(g, cs, persistDir, modules.ProdDependencies) 74 } 75 76 // NewCustomHostDB creates a HostDB using the provided dependencies. It loads the old 77 // persistence data, spawns the HostDB's scanning threads, and subscribes it to 78 // the consensusSet. 79 func NewCustomHostDB(g modules.Gateway, cs modules.ConsensusSet, persistDir string, deps modules.Dependencies) (*HostDB, error) { 80 // Create the HostDB object. 81 hdb := &HostDB{ 82 cs: cs, 83 deps: deps, 84 gateway: g, 85 persistDir: persistDir, 86 87 scanMap: make(map[string]struct{}), 88 } 89 90 // Create the persist directory if it does not yet exist. 91 err := os.MkdirAll(persistDir, 0700) 92 if err != nil { 93 return nil, err 94 } 95 96 // Create the logger. 97 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "hostdb.log")) 98 if err != nil { 99 return nil, err 100 } 101 hdb.log = logger 102 err = hdb.tg.AfterStop(func() error { 103 if err := hdb.log.Close(); err != nil { 104 // Resort to println as the logger is in an uncertain state. 105 fmt.Println("Failed to close the hostdb logger:", err) 106 return err 107 } 108 return nil 109 }) 110 if err != nil { 111 return nil, err 112 } 113 114 // The host tree is used to manage hosts and query them at random. 115 hdb.hostTree = hosttree.New(hdb.calculateHostWeight) 116 117 // Load the prior persistence structures. 118 hdb.mu.Lock() 119 err = hdb.load() 120 hdb.mu.Unlock() 121 if err != nil && !os.IsNotExist(err) { 122 return nil, err 123 } 124 err = hdb.tg.AfterStop(func() error { 125 hdb.mu.Lock() 126 err := hdb.saveSync() 127 hdb.mu.Unlock() 128 if err != nil { 129 hdb.log.Println("Unable to save the hostdb:", err) 130 return err 131 } 132 return nil 133 }) 134 if err != nil { 135 return nil, err 136 } 137 138 // Loading is complete, establish the save loop. 139 go hdb.threadedSaveLoop() 140 141 // Don't perform the remaining startup in the presence of a quitAfterLoad 142 // disruption. 143 if hdb.deps.Disrupt("quitAfterLoad") { 144 return hdb, nil 145 } 146 147 // COMPATv1.1.0 148 // 149 // If the block height has loaded as zero, the most recent consensus change 150 // needs to be set to perform a full rescan. This will also help the hostdb 151 // to pick up any hosts that it has incorrectly dropped in the past. 152 hdb.mu.Lock() 153 if hdb.blockHeight == 0 { 154 hdb.lastChange = modules.ConsensusChangeBeginning 155 } 156 hdb.mu.Unlock() 157 158 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 159 if err == modules.ErrInvalidConsensusChangeID { 160 // Subscribe again using the new ID. This will cause a triggered scan 161 // on all of the hosts, but that should be acceptable. 162 hdb.mu.Lock() 163 hdb.blockHeight = 0 164 hdb.lastChange = modules.ConsensusChangeBeginning 165 hdb.mu.Unlock() 166 err = cs.ConsensusSetSubscribe(hdb, hdb.lastChange, hdb.tg.StopChan()) 167 } 168 if err != nil { 169 return nil, errors.New("hostdb subscription failed: " + err.Error()) 170 } 171 err = hdb.tg.OnStop(func() error { 172 cs.Unsubscribe(hdb) 173 return nil 174 }) 175 if err != nil { 176 return nil, err 177 } 178 179 // Spawn the scan loop during production, but allow it to be disrupted 180 // during testing. Primary reason is so that we can fill the hostdb with 181 // fake hosts and not have them marked as offline as the scanloop operates. 182 if !hdb.deps.Disrupt("disableScanLoop") { 183 go hdb.threadedScan() 184 } else { 185 hdb.initialScanComplete = true 186 } 187 188 return hdb, nil 189 } 190 191 // ActiveHosts returns a list of hosts that are currently online, sorted by 192 // weight. 193 func (hdb *HostDB) ActiveHosts() (activeHosts []modules.HostDBEntry) { 194 allHosts := hdb.hostTree.All() 195 for _, entry := range allHosts { 196 if len(entry.ScanHistory) == 0 { 197 continue 198 } 199 if !entry.ScanHistory[len(entry.ScanHistory)-1].Success { 200 continue 201 } 202 if !entry.AcceptingContracts { 203 continue 204 } 205 activeHosts = append(activeHosts, entry) 206 } 207 return activeHosts 208 } 209 210 // AllHosts returns all of the hosts known to the hostdb, including the 211 // inactive ones. 212 func (hdb *HostDB) AllHosts() (allHosts []modules.HostDBEntry) { 213 return hdb.hostTree.All() 214 } 215 216 // AverageContractPrice returns the average price of a host. 217 func (hdb *HostDB) AverageContractPrice() (totalPrice types.Currency) { 218 sampleSize := 32 219 hosts := hdb.hostTree.SelectRandom(sampleSize, nil) 220 if len(hosts) == 0 { 221 return totalPrice 222 } 223 for _, host := range hosts { 224 totalPrice = totalPrice.Add(host.ContractPrice) 225 } 226 return totalPrice.Div64(uint64(len(hosts))) 227 } 228 229 // Close closes the hostdb, terminating its scanning threads 230 func (hdb *HostDB) Close() error { 231 return hdb.tg.Stop() 232 } 233 234 // Host returns the HostSettings associated with the specified NetAddress. If 235 // no matching host is found, Host returns false. 236 func (hdb *HostDB) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) { 237 host, exists := hdb.hostTree.Select(spk) 238 if !exists { 239 return host, exists 240 } 241 hdb.mu.RLock() 242 updateHostHistoricInteractions(&host, hdb.blockHeight) 243 hdb.mu.RUnlock() 244 return host, exists 245 } 246 247 // InitialScanComplete returns a boolean indicating if the initial scan of the 248 // hostdb is completed. 249 func (hdb *HostDB) InitialScanComplete() (complete bool, err error) { 250 if err = hdb.tg.Add(); err != nil { 251 return 252 } 253 defer hdb.tg.Done() 254 hdb.mu.Lock() 255 defer hdb.mu.Unlock() 256 complete = hdb.initialScanComplete 257 return 258 } 259 260 // RandomHosts implements the HostDB interface's RandomHosts() method. It takes 261 // a number of hosts to return, and a slice of netaddresses to ignore, and 262 // returns a slice of entries. 263 func (hdb *HostDB) RandomHosts(n int, excludeKeys []types.SiaPublicKey) ([]modules.HostDBEntry, error) { 264 hdb.mu.RLock() 265 initialScanComplete := hdb.initialScanComplete 266 hdb.mu.RUnlock() 267 if !initialScanComplete { 268 return []modules.HostDBEntry{}, ErrInitialScanIncomplete 269 } 270 return hdb.hostTree.SelectRandom(n, excludeKeys), nil 271 }