github.com/nebulouslabs/sia@v1.3.7/modules/host/host.go (about) 1 // Package host is an implementation of the host module, and is responsible for 2 // participating in the storage ecosystem, turning available disk space an 3 // internet bandwidth into profit for the user. 4 package host 5 6 // TODO: what happens if the renter submits the revision early, before the 7 // final revision. Will the host mark the contract as complete? 8 9 // TODO: Host and renter are reporting errors where the renter is not adding 10 // enough fees to the file contract. 11 12 // TODO: Test the safety of the builder, it should be okay to have multiple 13 // builders open for up to 600 seconds, which means multiple blocks could be 14 // received in that time period. Should also check what happens if a parent 15 // gets confirmed on the blockchain before the builder is finished. 16 17 // TODO: Double check that any network connection has a finite deadline - 18 // handling action items properly requires that the locks held on the 19 // obligations eventually be released. There's also some more advanced 20 // implementation that needs to happen with the storage obligation locks to 21 // make sure that someone who wants a lock is able to get it eventually. 22 23 // TODO: Add contract compensation from form contract to the storage obligation 24 // financial metrics, and to the host's tracking. 25 26 // TODO: merge the network interfaces stuff, don't forget to include the 27 // 'announced' variable as one of the outputs. 28 29 // TODO: 'announced' doesn't tell you if the announcement made it to the 30 // blockchain. 31 32 // TODO: Need to make sure that the revision exchange for the renter and the 33 // host is being handled correctly. For the host, it's not so difficult. The 34 // host need only send the most recent revision every time. But, the host 35 // should not sign a revision unless the renter has explicitly signed such that 36 // the 'WholeTransaction' fields cover only the revision and that the 37 // signatures for the revision don't depend on anything else. The renter needs 38 // to verify the same when checking on a file contract revision from the host. 39 // If the host has submitted a file contract revision where the signatures have 40 // signed the whole file contract, there is an issue. 41 42 // TODO: there is a mistake in the file contract revision rpc, the host, if it 43 // does not have the right file contract id, should be returning an error there 44 // to the renter (and not just to it's calling function without informing the 45 // renter what's up). 46 47 // TODO: Need to make sure that the correct height is being used when adding 48 // sectors to the storage manager - in some places right now WindowStart is 49 // being used but really it's WindowEnd that should be in use. 50 51 // TODO: The host needs some way to blacklist file contracts that are being 52 // abusive by repeatedly getting free download batches. 53 54 // TODO: clean up all of the magic numbers in the host. 55 56 // TODO: revamp the finances for the storage obligations. 57 58 // TODO: host_test.go has commented out tests. 59 60 // TODO: network_test.go has commented out tests. 61 62 // TODO: persist_test.go has commented out tests. 63 64 // TODO: update_test.go has commented out tests. 65 66 import ( 67 "errors" 68 "fmt" 69 "net" 70 "path/filepath" 71 "sync" 72 73 "github.com/NebulousLabs/Sia/build" 74 "github.com/NebulousLabs/Sia/crypto" 75 "github.com/NebulousLabs/Sia/modules" 76 "github.com/NebulousLabs/Sia/modules/host/contractmanager" 77 "github.com/NebulousLabs/Sia/persist" 78 siasync "github.com/NebulousLabs/Sia/sync" 79 "github.com/NebulousLabs/Sia/types" 80 ) 81 82 const ( 83 // Names of the various persistent files in the host. 84 dbFilename = modules.HostDir + ".db" 85 logFile = modules.HostDir + ".log" 86 settingsFile = modules.HostDir + ".json" 87 ) 88 89 var ( 90 // dbMetadata is a header that gets put into the database to identify a 91 // version and indicate that the database holds host information. 92 dbMetadata = persist.Metadata{ 93 Header: "Sia Host DB", 94 Version: "0.5.2", 95 } 96 97 // errHostClosed gets returned when a call is rejected due to the host 98 // having been closed. 99 errHostClosed = errors.New("call is disabled because the host is closed") 100 101 // Nil dependency errors. 102 errNilCS = errors.New("host cannot use a nil state") 103 errNilTpool = errors.New("host cannot use a nil transaction pool") 104 errNilWallet = errors.New("host cannot use a nil wallet") 105 106 // persistMetadata is the header that gets written to the persist file, and is 107 // used to recognize other persist files. 108 persistMetadata = persist.Metadata{ 109 Header: "Sia Host", 110 Version: "1.2.0", 111 } 112 ) 113 114 // A Host contains all the fields necessary for storing files for clients and 115 // performing the storage proofs on the received files. 116 type Host struct { 117 // RPC Metrics - atomic variables need to be placed at the top to preserve 118 // compatibility with 32bit systems. These values are not persistent. 119 atomicDownloadCalls uint64 120 atomicErroredCalls uint64 121 atomicFormContractCalls uint64 122 atomicRenewCalls uint64 123 atomicReviseCalls uint64 124 atomicSettingsCalls uint64 125 atomicUnrecognizedCalls uint64 126 127 // Error management. There are a few different types of errors returned by 128 // the host. These errors intentionally not persistent, so that the logging 129 // limits of each error type will be reset each time the host is reset. 130 // These values are not persistent. 131 atomicCommunicationErrors uint64 132 atomicConnectionErrors uint64 133 atomicConsensusErrors uint64 134 atomicInternalErrors uint64 135 atomicNormalErrors uint64 136 137 // Dependencies. 138 cs modules.ConsensusSet 139 tpool modules.TransactionPool 140 wallet modules.Wallet 141 dependencies modules.Dependencies 142 modules.StorageManager 143 144 // Host ACID fields - these fields need to be updated in serial, ACID 145 // transactions. 146 announced bool 147 announceConfirmed bool 148 blockHeight types.BlockHeight 149 publicKey types.SiaPublicKey 150 secretKey crypto.SecretKey 151 recentChange modules.ConsensusChangeID 152 unlockHash types.UnlockHash // A wallet address that can receive coins. 153 154 // Host transient fields - these fields are either determined at startup or 155 // otherwise are not critical to always be correct. 156 autoAddress modules.NetAddress // Determined using automatic tooling in network.go 157 financialMetrics modules.HostFinancialMetrics 158 settings modules.HostInternalSettings 159 revisionNumber uint64 160 workingStatus modules.HostWorkingStatus 161 connectabilityStatus modules.HostConnectabilityStatus 162 163 // A map of storage obligations that are currently being modified. Locks on 164 // storage obligations can be long-running, and each storage obligation can 165 // be locked separately. 166 lockedStorageObligations map[types.FileContractID]*siasync.TryMutex 167 168 // Utilities. 169 db *persist.BoltDatabase 170 listener net.Listener 171 log *persist.Logger 172 mu sync.RWMutex 173 persistDir string 174 port string 175 tg siasync.ThreadGroup 176 } 177 178 // checkUnlockHash will check that the host has an unlock hash. If the host 179 // does not have an unlock hash, an attempt will be made to get an unlock hash 180 // from the wallet. That may fail due to the wallet being locked, in which case 181 // an error is returned. 182 func (h *Host) checkUnlockHash() error { 183 addrs, err := h.wallet.AllAddresses() 184 if err != nil { 185 return err 186 } 187 hasAddr := false 188 for _, addr := range addrs { 189 if h.unlockHash == addr { 190 hasAddr = true 191 break 192 } 193 } 194 if !hasAddr || h.unlockHash == (types.UnlockHash{}) { 195 uc, err := h.wallet.NextAddress() 196 if err != nil { 197 return err 198 } 199 200 // Set the unlock hash and save the host. Saving is important, because 201 // the host will be using this unlock hash to establish identity, and 202 // losing it will mean silently losing part of the host identity. 203 h.unlockHash = uc.UnlockHash() 204 err = h.saveSync() 205 if err != nil { 206 return err 207 } 208 } 209 return nil 210 } 211 212 // newHost returns an initialized Host, taking a set of dependencies as input. 213 // By making the dependencies an argument of the 'new' call, the host can be 214 // mocked such that the dependencies can return unexpected errors or unique 215 // behaviors during testing, enabling easier testing of the failure modes of 216 // the Host. 217 func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) { 218 // Check that all the dependencies were provided. 219 if cs == nil { 220 return nil, errNilCS 221 } 222 if tpool == nil { 223 return nil, errNilTpool 224 } 225 if wallet == nil { 226 return nil, errNilWallet 227 } 228 229 // Create the host object. 230 h := &Host{ 231 cs: cs, 232 tpool: tpool, 233 wallet: wallet, 234 dependencies: dependencies, 235 236 lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex), 237 238 persistDir: persistDir, 239 } 240 241 // Call stop in the event of a partial startup. 242 var err error 243 defer func() { 244 if err != nil { 245 err = composeErrors(h.tg.Stop(), err) 246 } 247 }() 248 249 // Create the perist directory if it does not yet exist. 250 err = dependencies.MkdirAll(h.persistDir, 0700) 251 if err != nil { 252 return nil, err 253 } 254 255 // Initialize the logger, and set up the stop call that will close the 256 // logger. 257 h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile)) 258 if err != nil { 259 return nil, err 260 } 261 h.tg.AfterStop(func() { 262 err = h.log.Close() 263 if err != nil { 264 // State of the logger is uncertain, a Println will have to 265 // suffice. 266 fmt.Println("Error when closing the logger:", err) 267 } 268 }) 269 270 // Add the storage manager to the host, and set up the stop call that will 271 // close the storage manager. 272 h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager")) 273 if err != nil { 274 h.log.Println("Could not open the storage manager:", err) 275 return nil, err 276 } 277 h.tg.AfterStop(func() { 278 err = h.StorageManager.Close() 279 if err != nil { 280 h.log.Println("Could not close storage manager:", err) 281 } 282 }) 283 284 // Load the prior persistence structures, and configure the host to save 285 // before shutting down. 286 err = h.load() 287 if err != nil { 288 return nil, err 289 } 290 h.tg.AfterStop(func() { 291 err = h.saveSync() 292 if err != nil { 293 h.log.Println("Could not save host upon shutdown:", err) 294 } 295 }) 296 297 // Initialize the networking. We need to hold the lock while doing so since 298 // the previous load subscribed the host to the consenus set. 299 h.mu.Lock() 300 err = h.initNetworking(listenerAddress) 301 h.mu.Unlock() 302 if err != nil { 303 h.log.Println("Could not initialize host networking:", err) 304 return nil, err 305 } 306 return h, nil 307 } 308 309 // New returns an initialized Host. 310 func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) { 311 return newHost(modules.ProdDependencies, cs, tpool, wallet, address, persistDir) 312 } 313 314 // Close shuts down the host. 315 func (h *Host) Close() error { 316 return h.tg.Stop() 317 } 318 319 // ExternalSettings returns the hosts external settings. These values cannot be 320 // set by the user (host is configured through InternalSettings), and are the 321 // values that get displayed to other hosts on the network. 322 func (h *Host) ExternalSettings() modules.HostExternalSettings { 323 h.mu.Lock() 324 defer h.mu.Unlock() 325 err := h.tg.Add() 326 if err != nil { 327 build.Critical("Call to ExternalSettings after close") 328 } 329 defer h.tg.Done() 330 return h.externalSettings() 331 } 332 333 // WorkingStatus returns the working state of the host, where working is 334 // defined as having received more than workingStatusThreshold settings calls 335 // over the period of workingStatusFrequency. 336 func (h *Host) WorkingStatus() modules.HostWorkingStatus { 337 h.mu.RLock() 338 defer h.mu.RUnlock() 339 return h.workingStatus 340 } 341 342 // ConnectabilityStatus returns the connectability state of the host, whether 343 // the host can connect to itself on its configured netaddress. 344 func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus { 345 h.mu.RLock() 346 defer h.mu.RUnlock() 347 return h.connectabilityStatus 348 } 349 350 // FinancialMetrics returns information about the financial commitments, 351 // rewards, and activities of the host. 352 func (h *Host) FinancialMetrics() modules.HostFinancialMetrics { 353 h.mu.RLock() 354 defer h.mu.RUnlock() 355 err := h.tg.Add() 356 if err != nil { 357 build.Critical("Call to FinancialMetrics after close") 358 } 359 defer h.tg.Done() 360 return h.financialMetrics 361 } 362 363 // PublicKey returns the public key of the host that is used to facilitate 364 // relationships between the host and renter. 365 func (h *Host) PublicKey() types.SiaPublicKey { 366 h.mu.RLock() 367 defer h.mu.RUnlock() 368 return h.publicKey 369 } 370 371 // SetInternalSettings updates the host's internal HostInternalSettings object. 372 func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error { 373 h.mu.Lock() 374 defer h.mu.Unlock() 375 err := h.tg.Add() 376 if err != nil { 377 return err 378 } 379 defer h.tg.Done() 380 381 // The host should not be accepting file contracts if it does not have an 382 // unlock hash. 383 if settings.AcceptingContracts { 384 err := h.checkUnlockHash() 385 if err != nil { 386 return errors.New("internal settings not updated, no unlock hash: " + err.Error()) 387 } 388 } 389 390 if settings.NetAddress != "" { 391 err := settings.NetAddress.IsValid() 392 if err != nil { 393 return errors.New("internal settings not updated, invalid NetAddress: " + err.Error()) 394 } 395 } 396 397 // Check if the net address for the host has changed. If it has, and it's 398 // not equal to the auto address, then the host is going to need to make 399 // another blockchain announcement. 400 if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress { 401 h.announced = false 402 } 403 404 h.settings = settings 405 h.revisionNumber++ 406 407 err = h.saveSync() 408 if err != nil { 409 return errors.New("internal settings updated, but failed saving to disk: " + err.Error()) 410 } 411 return nil 412 } 413 414 // InternalSettings returns the settings of a host. 415 func (h *Host) InternalSettings() modules.HostInternalSettings { 416 h.mu.RLock() 417 defer h.mu.RUnlock() 418 err := h.tg.Add() 419 if err != nil { 420 return modules.HostInternalSettings{} 421 } 422 defer h.tg.Done() 423 return h.settings 424 }