gitlab.com/jokerrs1/Sia@v1.3.2/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 := h.wallet.AllAddresses() 184 hasAddr := false 185 for _, addr := range addrs { 186 if h.unlockHash == addr { 187 hasAddr = true 188 break 189 } 190 } 191 if !hasAddr || h.unlockHash == (types.UnlockHash{}) { 192 uc, err := h.wallet.NextAddress() 193 if err != nil { 194 return err 195 } 196 197 // Set the unlock hash and save the host. Saving is important, because 198 // the host will be using this unlock hash to establish identity, and 199 // losing it will mean silently losing part of the host identity. 200 h.unlockHash = uc.UnlockHash() 201 err = h.saveSync() 202 if err != nil { 203 return err 204 } 205 } 206 return nil 207 } 208 209 // newHost returns an initialized Host, taking a set of dependencies as input. 210 // By making the dependencies an argument of the 'new' call, the host can be 211 // mocked such that the dependencies can return unexpected errors or unique 212 // behaviors during testing, enabling easier testing of the failure modes of 213 // the Host. 214 func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) { 215 // Check that all the dependencies were provided. 216 if cs == nil { 217 return nil, errNilCS 218 } 219 if tpool == nil { 220 return nil, errNilTpool 221 } 222 if wallet == nil { 223 return nil, errNilWallet 224 } 225 226 // Create the host object. 227 h := &Host{ 228 cs: cs, 229 tpool: tpool, 230 wallet: wallet, 231 dependencies: dependencies, 232 233 lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex), 234 235 persistDir: persistDir, 236 } 237 238 // Call stop in the event of a partial startup. 239 var err error 240 defer func() { 241 if err != nil { 242 err = composeErrors(h.tg.Stop(), err) 243 } 244 }() 245 246 // Create the perist directory if it does not yet exist. 247 err = dependencies.MkdirAll(h.persistDir, 0700) 248 if err != nil { 249 return nil, err 250 } 251 252 // Initialize the logger, and set up the stop call that will close the 253 // logger. 254 h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile)) 255 if err != nil { 256 return nil, err 257 } 258 h.tg.AfterStop(func() { 259 err = h.log.Close() 260 if err != nil { 261 // State of the logger is uncertain, a Println will have to 262 // suffice. 263 fmt.Println("Error when closing the logger:", err) 264 } 265 }) 266 267 // Add the storage manager to the host, and set up the stop call that will 268 // close the storage manager. 269 h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager")) 270 if err != nil { 271 h.log.Println("Could not open the storage manager:", err) 272 return nil, err 273 } 274 h.tg.AfterStop(func() { 275 err = h.StorageManager.Close() 276 if err != nil { 277 h.log.Println("Could not close storage manager:", err) 278 } 279 }) 280 281 // Load the prior persistence structures, and configure the host to save 282 // before shutting down. 283 err = h.load() 284 if err != nil { 285 return nil, err 286 } 287 h.tg.AfterStop(func() { 288 err = h.saveSync() 289 if err != nil { 290 h.log.Println("Could not save host upon shutdown:", err) 291 } 292 }) 293 294 // Initialize the networking. 295 err = h.initNetworking(listenerAddress) 296 if err != nil { 297 h.log.Println("Could not initialize host networking:", err) 298 return nil, err 299 } 300 return h, nil 301 } 302 303 // New returns an initialized Host. 304 func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) { 305 return newHost(&modules.ProductionDependencies{}, cs, tpool, wallet, address, persistDir) 306 } 307 308 // Close shuts down the host. 309 func (h *Host) Close() error { 310 return h.tg.Stop() 311 } 312 313 // ExternalSettings returns the hosts external settings. These values cannot be 314 // set by the user (host is configured through InternalSettings), and are the 315 // values that get displayed to other hosts on the network. 316 func (h *Host) ExternalSettings() modules.HostExternalSettings { 317 h.mu.Lock() 318 defer h.mu.Unlock() 319 err := h.tg.Add() 320 if err != nil { 321 build.Critical("Call to ExternalSettings after close") 322 } 323 defer h.tg.Done() 324 return h.externalSettings() 325 } 326 327 // WorkingStatus returns the working state of the host, where working is 328 // defined as having received more than workingStatusThreshold settings calls 329 // over the period of workingStatusFrequency. 330 func (h *Host) WorkingStatus() modules.HostWorkingStatus { 331 h.mu.RLock() 332 defer h.mu.RUnlock() 333 return h.workingStatus 334 } 335 336 // ConnectabilityStatus returns the connectability state of the host, whether 337 // the host can connect to itself on its configured netaddress. 338 func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus { 339 h.mu.RLock() 340 defer h.mu.RUnlock() 341 return h.connectabilityStatus 342 } 343 344 // FinancialMetrics returns information about the financial commitments, 345 // rewards, and activities of the host. 346 func (h *Host) FinancialMetrics() modules.HostFinancialMetrics { 347 h.mu.RLock() 348 defer h.mu.RUnlock() 349 err := h.tg.Add() 350 if err != nil { 351 build.Critical("Call to FinancialMetrics after close") 352 } 353 defer h.tg.Done() 354 return h.financialMetrics 355 } 356 357 // PublicKey returns the public key of the host that is used to facilitate 358 // relationships between the host and renter. 359 func (h *Host) PublicKey() types.SiaPublicKey { 360 h.mu.RLock() 361 defer h.mu.RUnlock() 362 return h.publicKey 363 } 364 365 // SetInternalSettings updates the host's internal HostInternalSettings object. 366 func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error { 367 h.mu.Lock() 368 defer h.mu.Unlock() 369 err := h.tg.Add() 370 if err != nil { 371 return err 372 } 373 defer h.tg.Done() 374 375 // The host should not be accepting file contracts if it does not have an 376 // unlock hash. 377 if settings.AcceptingContracts { 378 err := h.checkUnlockHash() 379 if err != nil { 380 return errors.New("internal settings not updated, no unlock hash: " + err.Error()) 381 } 382 } 383 384 if settings.NetAddress != "" { 385 err := settings.NetAddress.IsValid() 386 if err != nil { 387 return errors.New("internal settings not updated, invalid NetAddress: " + err.Error()) 388 } 389 } 390 391 // Check if the net address for the host has changed. If it has, and it's 392 // not equal to the auto address, then the host is going to need to make 393 // another blockchain announcement. 394 if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress { 395 h.announced = false 396 } 397 398 h.settings = settings 399 h.revisionNumber++ 400 401 err = h.saveSync() 402 if err != nil { 403 return errors.New("internal settings updated, but failed saving to disk: " + err.Error()) 404 } 405 return nil 406 } 407 408 // InternalSettings returns the settings of a host. 409 func (h *Host) InternalSettings() modules.HostInternalSettings { 410 h.mu.RLock() 411 defer h.mu.RUnlock() 412 err := h.tg.Add() 413 if err != nil { 414 return modules.HostInternalSettings{} 415 } 416 defer h.tg.Done() 417 return h.settings 418 }