gitlab.com/SiaPrime/SiaPrime@v1.4.1/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 "gitlab.com/SiaPrime/SiaPrime/build" 74 "gitlab.com/SiaPrime/SiaPrime/crypto" 75 "gitlab.com/SiaPrime/SiaPrime/modules" 76 "gitlab.com/SiaPrime/SiaPrime/modules/host/contractmanager" 77 "gitlab.com/SiaPrime/SiaPrime/persist" 78 siasync "gitlab.com/SiaPrime/SiaPrime/sync" 79 "gitlab.com/SiaPrime/SiaPrime/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 errNilGateway = errors.New("host cannot use nil gateway") 106 107 // persistMetadata is the header that gets written to the persist file, and is 108 // used to recognize other persist files. 109 persistMetadata = persist.Metadata{ 110 Header: "Sia Host", 111 Version: "1.2.0", 112 } 113 ) 114 115 // A Host contains all the fields necessary for storing files for clients and 116 // performing the storage proofs on the received files. 117 type Host struct { 118 // RPC Metrics - atomic variables need to be placed at the top to preserve 119 // compatibility with 32bit systems. These values are not persistent. 120 atomicDownloadCalls uint64 121 atomicErroredCalls uint64 122 atomicFormContractCalls uint64 123 atomicRenewCalls uint64 124 atomicReviseCalls uint64 125 atomicSettingsCalls uint64 126 atomicUnrecognizedCalls uint64 127 128 // Error management. There are a few different types of errors returned by 129 // the host. These errors intentionally not persistent, so that the logging 130 // limits of each error type will be reset each time the host is reset. 131 // These values are not persistent. 132 atomicCommunicationErrors uint64 133 atomicConnectionErrors uint64 134 atomicConsensusErrors uint64 135 atomicInternalErrors uint64 136 atomicNormalErrors uint64 137 138 // Dependencies. 139 cs modules.ConsensusSet 140 g modules.Gateway 141 tpool modules.TransactionPool 142 wallet modules.Wallet 143 dependencies modules.Dependencies 144 modules.StorageManager 145 146 // Host ACID fields - these fields need to be updated in serial, ACID 147 // transactions. 148 announced bool 149 announceConfirmed bool 150 blockHeight types.BlockHeight 151 publicKey types.SiaPublicKey 152 secretKey crypto.SecretKey 153 recentChange modules.ConsensusChangeID 154 unlockHash types.UnlockHash // A wallet address that can receive coins. 155 156 // Host transient fields - these fields are either determined at startup or 157 // otherwise are not critical to always be correct. 158 autoAddress modules.NetAddress // Determined using automatic tooling in network.go 159 financialMetrics modules.HostFinancialMetrics 160 settings modules.HostInternalSettings 161 revisionNumber uint64 162 workingStatus modules.HostWorkingStatus 163 connectabilityStatus modules.HostConnectabilityStatus 164 165 // A map of storage obligations that are currently being modified. Locks on 166 // storage obligations can be long-running, and each storage obligation can 167 // be locked separately. 168 lockedStorageObligations map[types.FileContractID]*siasync.TryMutex 169 170 // Utilities. 171 db *persist.BoltDatabase 172 listener net.Listener 173 log *persist.Logger 174 mu sync.RWMutex 175 persistDir string 176 port string 177 tg siasync.ThreadGroup 178 } 179 180 // checkUnlockHash will check that the host has an unlock hash. If the host 181 // does not have an unlock hash, an attempt will be made to get an unlock hash 182 // from the wallet. That may fail due to the wallet being locked, in which case 183 // an error is returned. 184 func (h *Host) checkUnlockHash() error { 185 addrs, err := h.wallet.AllAddresses() 186 if err != nil { 187 return err 188 } 189 hasAddr := false 190 for _, addr := range addrs { 191 if h.unlockHash == addr { 192 hasAddr = true 193 break 194 } 195 } 196 if !hasAddr || h.unlockHash == (types.UnlockHash{}) { 197 uc, err := h.wallet.NextAddress() 198 if err != nil { 199 return err 200 } 201 202 // Set the unlock hash and save the host. Saving is important, because 203 // the host will be using this unlock hash to establish identity, and 204 // losing it will mean silently losing part of the host identity. 205 h.unlockHash = uc.UnlockHash() 206 err = h.saveSync() 207 if err != nil { 208 return err 209 } 210 } 211 return nil 212 } 213 214 // newHost returns an initialized Host, taking a set of dependencies as input. 215 // By making the dependencies an argument of the 'new' call, the host can be 216 // mocked such that the dependencies can return unexpected errors or unique 217 // behaviors during testing, enabling easier testing of the failure modes of 218 // the Host. 219 func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, g modules.Gateway, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) { 220 // Check that all the dependencies were provided. 221 if cs == nil { 222 return nil, errNilCS 223 } 224 if g == nil { 225 return nil, errNilGateway 226 } 227 if tpool == nil { 228 return nil, errNilTpool 229 } 230 if wallet == nil { 231 return nil, errNilWallet 232 } 233 234 // Create the host object. 235 h := &Host{ 236 cs: cs, 237 g: g, 238 tpool: tpool, 239 wallet: wallet, 240 dependencies: dependencies, 241 242 lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex), 243 244 persistDir: persistDir, 245 } 246 247 // Call stop in the event of a partial startup. 248 var err error 249 defer func() { 250 if err != nil { 251 err = composeErrors(h.tg.Stop(), err) 252 } 253 }() 254 255 // Create the perist directory if it does not yet exist. 256 err = dependencies.MkdirAll(h.persistDir, 0700) 257 if err != nil { 258 return nil, err 259 } 260 261 // Initialize the logger, and set up the stop call that will close the 262 // logger. 263 h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile)) 264 if err != nil { 265 return nil, err 266 } 267 h.tg.AfterStop(func() { 268 err = h.log.Close() 269 if err != nil { 270 // State of the logger is uncertain, a Println will have to 271 // suffice. 272 fmt.Println("Error when closing the logger:", err) 273 } 274 }) 275 276 // Add the storage manager to the host, and set up the stop call that will 277 // close the storage manager. 278 h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager")) 279 if err != nil { 280 h.log.Println("Could not open the storage manager:", err) 281 return nil, err 282 } 283 h.tg.AfterStop(func() { 284 err = h.StorageManager.Close() 285 if err != nil { 286 h.log.Println("Could not close storage manager:", err) 287 } 288 }) 289 290 // Load the prior persistence structures, and configure the host to save 291 // before shutting down. 292 err = h.load() 293 if err != nil { 294 return nil, err 295 } 296 h.tg.AfterStop(func() { 297 err = h.saveSync() 298 if err != nil { 299 h.log.Println("Could not save host upon shutdown:", err) 300 } 301 }) 302 303 // Ensure the host is consistent by pruning any stale storage obligations. 304 if err := h.PruneStaleStorageObligations(); err != nil { 305 h.log.Println("Could not prune stale storage obligations:", err) 306 return nil, err 307 } 308 309 // Initialize the networking. We need to hold the lock while doing so since 310 // the previous load subscribed the host to the consensus set. 311 h.mu.Lock() 312 err = h.initNetworking(listenerAddress) 313 h.mu.Unlock() 314 if err != nil { 315 h.log.Println("Could not initialize host networking:", err) 316 return nil, err 317 } 318 return h, nil 319 } 320 321 // New returns an initialized Host. 322 func New(cs modules.ConsensusSet, g modules.Gateway, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) { 323 return newHost(modules.ProdDependencies, cs, g, tpool, wallet, address, persistDir) 324 } 325 326 // Close shuts down the host. 327 func (h *Host) Close() error { 328 return h.tg.Stop() 329 } 330 331 // ExternalSettings returns the hosts external settings. These values cannot be 332 // set by the user (host is configured through InternalSettings), and are the 333 // values that get displayed to other hosts on the network. 334 func (h *Host) ExternalSettings() modules.HostExternalSettings { 335 h.mu.Lock() 336 defer h.mu.Unlock() 337 err := h.tg.Add() 338 if err != nil { 339 build.Critical("Call to ExternalSettings after close") 340 } 341 defer h.tg.Done() 342 return h.externalSettings() 343 } 344 345 // WorkingStatus returns the working state of the host, where working is 346 // defined as having received more than workingStatusThreshold settings calls 347 // over the period of workingStatusFrequency. 348 func (h *Host) WorkingStatus() modules.HostWorkingStatus { 349 h.mu.RLock() 350 defer h.mu.RUnlock() 351 return h.workingStatus 352 } 353 354 // ConnectabilityStatus returns the connectability state of the host, whether 355 // the host can connect to itself on its configured netaddress. 356 func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus { 357 h.mu.RLock() 358 defer h.mu.RUnlock() 359 return h.connectabilityStatus 360 } 361 362 // FinancialMetrics returns information about the financial commitments, 363 // rewards, and activities of the host. 364 func (h *Host) FinancialMetrics() modules.HostFinancialMetrics { 365 h.mu.RLock() 366 defer h.mu.RUnlock() 367 err := h.tg.Add() 368 if err != nil { 369 build.Critical("Call to FinancialMetrics after close") 370 } 371 defer h.tg.Done() 372 return h.financialMetrics 373 } 374 375 // PublicKey returns the public key of the host that is used to facilitate 376 // relationships between the host and renter. 377 func (h *Host) PublicKey() types.SiaPublicKey { 378 h.mu.RLock() 379 defer h.mu.RUnlock() 380 return h.publicKey 381 } 382 383 // SetInternalSettings updates the host's internal HostInternalSettings object. 384 func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error { 385 h.mu.Lock() 386 defer h.mu.Unlock() 387 err := h.tg.Add() 388 if err != nil { 389 return err 390 } 391 defer h.tg.Done() 392 393 // The host should not be accepting file contracts if it does not have an 394 // unlock hash. 395 if settings.AcceptingContracts { 396 err := h.checkUnlockHash() 397 if err != nil { 398 return errors.New("internal settings not updated, no unlock hash: " + err.Error()) 399 } 400 } 401 402 if settings.NetAddress != "" { 403 err := settings.NetAddress.IsValid() 404 if err != nil { 405 return errors.New("internal settings not updated, invalid NetAddress: " + err.Error()) 406 } 407 } 408 409 // Check if the net address for the host has changed. If it has, and it's 410 // not equal to the auto address, then the host is going to need to make 411 // another blockchain announcement. 412 if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress { 413 h.announced = false 414 } 415 416 h.settings = settings 417 h.revisionNumber++ 418 419 err = h.saveSync() 420 if err != nil { 421 return errors.New("internal settings updated, but failed saving to disk: " + err.Error()) 422 } 423 return nil 424 } 425 426 // InternalSettings returns the settings of a host. 427 func (h *Host) InternalSettings() modules.HostInternalSettings { 428 h.mu.RLock() 429 defer h.mu.RUnlock() 430 err := h.tg.Add() 431 if err != nil { 432 return modules.HostInternalSettings{} 433 } 434 defer h.tg.Done() 435 return h.settings 436 }