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