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