github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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: Test the safety of the builder, it should be okay to have multiple 7 // builders open for up to 600 seconds, which means multiple blocks could be 8 // received in that time period. Should also check what happens if a prent gets 9 // confirmed on the blockchain before the builder is finished. 10 11 // TODO: Would be nice to have some sort of error transport to the user, so 12 // that the user is notified in ways other than logs via the host that there 13 // are issues such as disk, etc. 14 15 // TODO: automated_settings.go, a file which can be responsible for 16 // automatically regulating things like bandwidth price, storage price, 17 // contract price, etc. One of the features in consideration is that the host 18 // would start to steeply increase the contract price as it begins to run low 19 // on collateral. The host would also inform the user that there doesn't seem 20 // to be enough money to handle all of the file contracts, so that the user 21 // could make a judgement call on whether to get more. 22 23 // TODO: The host needs to somehow keep an awareness of its bandwidth limits, 24 // and needs to reject calls if there is not enough bandwidth available. 25 26 // TODO: The synchronization on the port forwarding is not perfect. Sometimes a 27 // port will be cleared before it was set (if things happen fast enough), 28 // because the port forwarding call is asynchronous. 29 30 // TODO: Add contract compensation from form contract to the storage obligation 31 // financial metrics, and to the host's tracking. 32 33 // TODO: merge the network interfaces stuff, don't forget to include the 34 // 'announced' variable as one of the outputs. 35 36 // TODO: check that the host is doing proper clean shudown, especially 37 // network.go, a couple of problems with clean shutdown in network.go. 38 39 // TODO: 'announced' doesn't tell you if the announcement made it to the 40 // blockchain. 41 42 // TODO: Need to make sure that the revision exchange for the renter and the 43 // host is being handled correctly. For the host, it's not so difficult. The 44 // host need only send the most recent revision every time. But, the host 45 // should not sign a revision unless the renter has explicitly signed such that 46 // the 'WholeTransaction' fields cover only the revision and that the 47 // signatures for the revision don't depend on anything else. The renter needs 48 // to verify the same when checking on a file contract revision from the host. 49 // If the host has submitted a file contract revision where the signatures have 50 // signed the whole file contract, there is an issue. 51 52 // TODO: there is a mistake in the file contract revision rpc, the host, if it 53 // does not have the right file contract id, should be returning an error there 54 // to the renter (and not just to it's calling function without informing the 55 // renter what's up). 56 57 // TODO: Need to make sure that the correct height is being used when adding 58 // sectors to the storage manager - in some places right now WindowStart is 59 // being used but really it's WindowEnd that should be in use. 60 61 // TODO: clean up all of the magic numbers in the host. 62 63 // TODO: revamp the finances for the storage obligations. 64 65 // TODO: host_test.go has commented out tests. 66 67 // TODO: network_test.go has commented out tests. 68 69 // TODO: persist_test.go has commented out tests. 70 71 // TODO: update_test.go has commented out tests. 72 73 import ( 74 "errors" 75 "net" 76 "path/filepath" 77 "sync" 78 79 "github.com/NebulousLabs/Sia/crypto" 80 "github.com/NebulousLabs/Sia/modules" 81 "github.com/NebulousLabs/Sia/modules/host/storagemanager" 82 "github.com/NebulousLabs/Sia/persist" 83 "github.com/NebulousLabs/Sia/types" 84 ) 85 86 const ( 87 // Names of the various persistent files in the host. 88 dbFilename = modules.HostDir + ".db" 89 logFile = modules.HostDir + ".log" 90 settingsFile = modules.HostDir + ".json" 91 ) 92 93 var ( 94 // dbMetadata is a header that gets put into the database to identify a 95 // version and indicate that the database holds host information. 96 dbMetadata = persist.Metadata{ 97 Header: "Sia Host DB", 98 Version: "0.5.2", 99 } 100 101 // persistMetadata is the header that gets written to the persist file, and is 102 // used to recognize other persist files. 103 persistMetadata = persist.Metadata{ 104 Header: "Sia Host", 105 Version: "0.5", 106 } 107 108 // errHostClosed gets returned when a call is rejected due to the host 109 // having been closed. 110 errHostClosed = errors.New("call is disabled because the host is closed") 111 112 // Nil dependency errors. 113 errNilCS = errors.New("host cannot use a nil state") 114 errNilTpool = errors.New("host cannot use a nil transaction pool") 115 errNilWallet = errors.New("host cannot use a nil wallet") 116 ) 117 118 // A Host contains all the fields necessary for storing files for clients and 119 // performing the storage proofs on the received files. 120 type Host struct { 121 // RPC Metrics - atomic variables need to be placed at the top to preserve 122 // compatibility with 32bit systems. 123 atomicDownloadCalls uint64 124 atomicErroredCalls uint64 125 atomicFormContractCalls uint64 126 atomicRenewCalls uint64 127 atomicReviseCalls uint64 128 atomicRecentRevisionCalls uint64 129 atomicSettingsCalls uint64 130 atomicUnrecognizedCalls uint64 131 132 // Dependencies. 133 cs modules.ConsensusSet 134 tpool modules.TransactionPool 135 wallet modules.Wallet 136 dependencies 137 modules.StorageManager 138 139 // Consensus Tracking. 140 blockHeight types.BlockHeight 141 recentChange modules.ConsensusChangeID 142 143 // Host Identity 144 // 145 // The revision number keeps track of the current revision number on the 146 // host external settingse 147 // 148 // The auto address is the address that is fetched automatically by the 149 // host. The host will ignore the automatic address if settings.NetAddress 150 // has been set by the user. If settings.NetAddress is blank, then the host 151 // will track its own ip address and make an announcement on the blockchain 152 // every time that the address changes. 153 // 154 // The announced bool indicates whether the host remembers having a 155 // successful announcement with the current address. 156 announced bool 157 autoAddress modules.NetAddress 158 financialMetrics modules.HostFinancialMetrics 159 publicKey types.SiaPublicKey 160 revisionNumber uint64 161 secretKey crypto.SecretKey 162 settings modules.HostInternalSettings 163 unlockHash types.UnlockHash // A wallet address that can receive coins. 164 165 // Storage Obligation Management - different from file management in that 166 // the storage obligation management is the new way of handling storage 167 // obligations. Is a replacement for the contract obligation logic, but the 168 // old logic is being kept for compatibility purposes. 169 // 170 // Storage is broken up into sectors. The sectors are distributed across a 171 // set of storage folders using a strategy that tries to create even 172 // distributions, but not aggressively. Uneven distributions could be 173 // manufactured by an attacker given sufficient knowledge about the disk 174 // layout (knowledge which should be unavailable), but a limited amount of 175 // damage can be done even with this attack. 176 lockedStorageObligations map[types.FileContractID]struct{} // Which storage obligations are currently being modified. 177 178 // Utilities. 179 db *persist.BoltDatabase 180 listener net.Listener 181 log *persist.Logger 182 mu sync.RWMutex 183 persistDir string 184 port string 185 186 // The resource lock is held by threaded functions for the duration of 187 // their operation. Functions should grab the resource lock as a read lock 188 // unless they are planning on manipulating the 'closed' variable. 189 // Readlocks are used so that multiple functions can use resources 190 // simultaneously, but the resources are not closed until all functions 191 // accessing them have returned. 192 closed bool 193 resourceLock sync.RWMutex 194 } 195 196 // checkUnlockHash will check that the host has an unlock hash. If the host 197 // does not have an unlock hash, an attempt will be made to get an unlock hash 198 // from the wallet. That may fail due to the wallet being locked, in which case 199 // an error is returned. 200 func (h *Host) checkUnlockHash() error { 201 if h.unlockHash == (types.UnlockHash{}) { 202 uc, err := h.wallet.NextAddress() 203 if err != nil { 204 return err 205 } 206 207 // Set the unlock hash and save the host. Saving is important, because 208 // the host will be using this unlock hash to establish identity, and 209 // losing it will mean silently losing part of the host identity. 210 h.unlockHash = uc.UnlockHash() 211 err = h.save() 212 if err != nil { 213 return err 214 } 215 } 216 return nil 217 } 218 219 // newHost returns an initialized Host, taking a set of dependencies as input. 220 // By making the dependencies an argument of the 'new' call, the host can be 221 // mocked such that the dependencies can return unexpected errors or unique 222 // behaviors during testing, enabling easier testing of the failure modes of 223 // the Host. 224 func newHost(dependencies dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) { 225 // Check that all the dependencies were provided. 226 if cs == nil { 227 return nil, errNilCS 228 } 229 if tpool == nil { 230 return nil, errNilTpool 231 } 232 if wallet == nil { 233 return nil, errNilWallet 234 } 235 236 // Create the host object. 237 h := &Host{ 238 cs: cs, 239 tpool: tpool, 240 wallet: wallet, 241 dependencies: dependencies, 242 243 lockedStorageObligations: make(map[types.FileContractID]struct{}), 244 245 persistDir: persistDir, 246 } 247 248 var err error 249 250 // Add the storage manager to the host. 251 // 252 // TODO: instead of hardcoding a storage manager, the storage manager 253 // should probably be chosen by the person that calls 'New', same way that 254 // the wallet, transaction pool, and consensus set are. 255 h.StorageManager, err = storagemanager.New(filepath.Join(persistDir, "storagemanager")) 256 if err != nil { 257 return nil, err 258 } 259 260 // Create the perist directory if it does not yet exist. 261 err = dependencies.mkdirAll(h.persistDir, 0700) 262 if err != nil { 263 return nil, err 264 } 265 266 // Initialize the logger. Logging should be initialized ASAP, because the 267 // rest of the initialization makes use of the logger. 268 h.log, err = dependencies.newLogger(filepath.Join(h.persistDir, logFile)) 269 if err != nil { 270 return nil, err 271 } 272 273 // Open the database containing the host's storage obligation metadata. 274 h.db, err = dependencies.openDatabase(dbMetadata, filepath.Join(h.persistDir, dbFilename)) 275 if err != nil { 276 // An error will be returned if the database has the wrong version, but 277 // as of writing there was only one version of the database and all 278 // other databases would be incompatible. 279 _ = h.log.Close() 280 return nil, err 281 } 282 // After opening the database, it must be initialized. Most commonly, 283 // nothing happens. But for new databases, a set of buckets must be 284 // created. Initialization is also a good time to run sanity checks. 285 err = h.initDB() 286 if err != nil { 287 _ = h.log.Close() 288 _ = h.db.Close() 289 return nil, err 290 } 291 292 // Load the prior persistence structures. 293 err = h.load() 294 if err != nil { 295 _ = h.log.Close() 296 _ = h.db.Close() 297 return nil, err 298 } 299 300 // Get the host established on the network. 301 err = h.initNetworking(listenerAddress) 302 if err != nil { 303 _ = h.log.Close() 304 _ = h.db.Close() 305 return nil, err 306 } 307 308 return h, nil 309 } 310 311 // New returns an initialized Host. 312 func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) { 313 return newHost(productionDependencies{}, cs, tpool, wallet, address, persistDir) 314 } 315 316 // Close shuts down the host, preparing it for garbage collection. 317 func (h *Host) Close() (composedError error) { 318 // Unsubscribe the host from the consensus set. Call will not terminate 319 // until the last consensus update has been sent to the host. 320 // Unsubscription must happen before any resources are released or 321 // terminated because the process consensus change function makes use of 322 // those resources. 323 h.cs.Unsubscribe(h) 324 325 // Close the listener, which means incoming network connections will be 326 // rejected. The listener should be closed before the host resources are 327 // disabled, as incoming connections will want to use the hosts resources. 328 err := h.listener.Close() 329 if err != nil { 330 composedError = composeErrors(composedError, err) 331 } 332 333 // Grab the resource lock and indicate that the host is closing. Concurrent 334 // functions hold the resource lock until they terminate, meaning that no 335 // threaded function will be running by the time the resource lock is 336 // acquired. 337 h.resourceLock.Lock() 338 h.closed = true 339 h.resourceLock.Unlock() 340 341 err = h.StorageManager.Close() 342 if err != nil { 343 composedError = composeErrors(composedError, err) 344 } 345 346 // Close the bolt database. 347 err = h.db.Close() 348 if err != nil { 349 composedError = composeErrors(composedError, err) 350 } 351 352 // Clear the port that was forwarded at startup. The port handling must 353 // happen before the logger is closed, as it leaves a logging message. 354 err = h.managedClearPort() 355 if err != nil { 356 composedError = composeErrors(composedError, err) 357 } 358 359 // Save the latest host state. 360 h.mu.Lock() 361 err = h.saveSync() 362 h.mu.Unlock() 363 if err != nil { 364 composedError = composeErrors(composedError, err) 365 } 366 367 // Close the logger. The logger should be the last thing to shut down so 368 // that all other objects have access to logging while closing. 369 err = h.log.Close() 370 if err != nil { 371 composedError = composeErrors(composedError, err) 372 } 373 return composedError 374 } 375 376 // ExternalSettings returns the hosts external settings. These values cannot be 377 // set by the user (host is configured through InternalSettings), and are the 378 // values that get displayed to other hosts on the network. 379 func (h *Host) ExternalSettings() modules.HostExternalSettings { 380 h.mu.RLock() 381 defer h.mu.RUnlock() 382 return h.externalSettings() 383 } 384 385 // FinancialMetrics returns information about the financial commitments, 386 // rewards, and activities of the host. 387 func (h *Host) FinancialMetrics() modules.HostFinancialMetrics { 388 h.mu.RLock() 389 defer h.mu.RUnlock() 390 return h.financialMetrics 391 } 392 393 // SetInternalSettings updates the host's internal HostInternalSettings object. 394 func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error { 395 h.mu.Lock() 396 defer h.mu.Unlock() 397 h.resourceLock.RLock() 398 defer h.resourceLock.RUnlock() 399 if h.closed { 400 return errHostClosed 401 } 402 403 // The host should not be accepting file contracts if it does not have an 404 // unlock hash. 405 if settings.AcceptingContracts { 406 err := h.checkUnlockHash() 407 if err != nil { 408 return errors.New("internal settings not updated, no unlock hash: " + err.Error()) 409 } 410 } 411 412 if settings.NetAddress != "" { 413 err := settings.NetAddress.IsValid() 414 if err != nil { 415 return errors.New("internal settings not updated, invalid NetAddress: " + err.Error()) 416 } 417 } 418 419 // Check if the net address for the host has changed. If it has, and it's 420 // not equal to the auto address, then the host is going to need to make 421 // another blockchain announcement. 422 if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress { 423 h.announced = false 424 } 425 426 h.settings = settings 427 h.revisionNumber++ 428 429 err := h.saveSync() 430 if err != nil { 431 return errors.New("internal settings updated, but failed saving to disk: " + err.Error()) 432 } 433 return nil 434 } 435 436 // InternalSettings returns the settings of a host. 437 func (h *Host) InternalSettings() modules.HostInternalSettings { 438 h.mu.RLock() 439 defer h.mu.RUnlock() 440 return h.settings 441 }