Skip to content
Snippets Groups Projects
Select Git revision
  • manage_sherd_folders
  • master default protected
  • host_financial_metrics
  • err-not-found
  • debug_maxcollateral_zero
  • add_providerlist_in_host_api
  • wallet_start_if_transporter_unavail
  • test-forks
  • debug-prints
  • disable_old_tls
  • wallet-improve-defrag
  • 231-reject-zero-storageprice-setting
  • client-ctx
  • fork2-spfb-geo-review
  • attach-bad-file-size-logs
  • renewal-debug-logs
  • create_sectorfile_as_sparse
  • cakiwi-changes-1
  • 216-externalize-build-tags
  • new-ci-integration
  • v1.9.3
  • v1.9.2
  • v1.9.1
  • v1.9.0
  • v1.8.4
  • v1.8.3
  • v1.8.0
  • v1.7.2
  • v1.7.1
  • v1.7.0
  • v1.6.6
  • v1.6.5
  • v1.6.4
  • v1.6.3.1
  • v1.6.3
  • v1.6.2
  • 1.6.1
  • v1.6.0
  • v1.5.3
  • v1.5.2
40 results

host.go

Code owners
Assign users and groups as approvers for specific file changes. Learn more.
host.go 5.98 KiB
package host

import (
	"errors"
	"net"
	"os"
	"sync"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/consensus"
	safesync "github.com/NebulousLabs/Sia/sync"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// StorageProofReorgDepth states how many blocks to wait before submitting
	// a storage proof. This reduces the chance of needing to resubmit because
	// of a reorg.
	StorageProofReorgDepth = 10
	maxContractLen         = 1 << 16 // The maximum allowed size of a file contract coming in over the wire. This does not include the file.
)

// A contractObligation tracks a file contract that the host is obligated to
// fulfill.
type contractObligation struct {
	ID           types.FileContractID
	FileContract types.FileContract
	Path         string // Where on disk the file is stored.

	// each obligation needs a mutex to prevent simultaneous revisions to the
	// same obligation
	mu *sync.Mutex
}

// A Host contains all the fields necessary for storing files for clients and
// performing the storage proofs on the received files.
type Host struct {
	cs          *consensus.ConsensusSet
	hostdb      modules.HostDB
	tpool       modules.TransactionPool
	wallet      modules.Wallet
	blockHeight types.BlockHeight

	consensusHeight types.BlockHeight
	myAddr          modules.NetAddress
	saveDir         string
	spaceRemaining  int64
	fileCounter     int
	profit          types.Currency

	listener net.Listener

	obligationsByID     map[types.FileContractID]contractObligation
	obligationsByHeight map[types.BlockHeight][]contractObligation
	secretKey           crypto.SecretKey
	publicKey           types.SiaPublicKey

	modules.HostSettings

	mu *safesync.RWMutex
}

// New returns an initialized Host.
func New(cs *consensus.ConsensusSet, hdb modules.HostDB, tpool modules.TransactionPool, wallet modules.Wallet, addr string, saveDir string) (*Host, error) {
	if cs == nil {
		return nil, errors.New("host cannot use a nil state")
	}
	if hdb == nil {
		return nil, errors.New("host cannot use a nil hostdb")
	}
	if tpool == nil {
		return nil, errors.New("host cannot use a nil tpool")
	}
	if wallet == nil {
		return nil, errors.New("host cannot use a nil wallet")
	}

	// Create host directory if it does not exist.
	err := os.MkdirAll(saveDir, 0700)
	if err != nil {
		return nil, err
	}

	h := &Host{
		cs:     cs,
		hostdb: hdb,
		tpool:  tpool,
		wallet: wallet,

		// default host settings
		HostSettings: modules.HostSettings{
			TotalStorage: 10e9,                        // 10 GB
			MaxFilesize:  1e9,                         // 1 GB
			MaxDuration:  144 * 60,                    // 60 days
			WindowSize:   288,                         // 48 hours
			Price:        types.NewCurrency64(100e12), // 0.1 siacoin / mb / week
			Collateral:   types.NewCurrency64(0),
		},

		saveDir: saveDir,

		obligationsByID:     make(map[types.FileContractID]contractObligation),
		obligationsByHeight: make(map[types.BlockHeight][]contractObligation),

		mu: safesync.New(modules.SafeMutexDelay, 1),
	}
	h.spaceRemaining = h.TotalStorage

	// Generate signing key, for revising contracts.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		return nil, err
	}
	h.secretKey = sk
	h.publicKey = types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}

	// Load the old host data.
	err = h.load()
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// Create listener and set address.
	h.listener, err = net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}
	_, port, _ := net.SplitHostPort(h.listener.Addr().String())
	h.myAddr = modules.NetAddress(net.JoinHostPort("::1", port))

	// Learn our external IP.
	go h.learnHostname()

	// Forward the hosting port, if possible.
	go h.forwardPort(port)
	// spawn listener
	go h.listen()

	h.cs.ConsensusSetSubscribe(h)

	return h, nil
}

// SetConfig updates the host's internal HostSettings object. To modify
// a specific field, use a combination of Info and SetConfig
func (h *Host) SetSettings(settings modules.HostSettings) {
	lockID := h.mu.Lock()
	defer h.mu.Unlock(lockID)
	h.spaceRemaining += settings.TotalStorage - h.TotalStorage
	h.HostSettings = settings
	h.save()
}

// Settings returns the settings of a host.
func (h *Host) Settings() modules.HostSettings {
	lockID := h.mu.RLock()
	defer h.mu.RUnlock(lockID)
	return h.HostSettings
}

func (h *Host) Address() modules.NetAddress {
	// no lock needed; h.myAddr is only set once (in New).
	return h.myAddr
}

func (h *Host) Info() modules.HostInfo {
	lockID := h.mu.RLock()
	defer h.mu.RUnlock(lockID)

	info := modules.HostInfo{
		HostSettings: h.HostSettings,

		StorageRemaining: h.spaceRemaining,
		NumContracts:     len(h.obligationsByID),
		Profit:           h.profit,
	}
	// sum up the current obligations to calculate PotentialProfit
	for _, obligation := range h.obligationsByID {
		fc := obligation.FileContract
		info.PotentialProfit = info.PotentialProfit.Add(fc.Payout.Sub(fc.Tax()))
	}

	// Calculate estimated competition (reported in per GB per month). Price
	// calculated by taking the average of 8 ranomly selected weighted hosts.
	var averagePrice types.Currency
	// TODO: 8 is the sample size - to be made a constant.
	hosts := h.hostdb.RandomHosts(8)
	for _, host := range hosts {
		averagePrice = averagePrice.Add(host.Price)
	}
	if len(hosts) == 0 {
		return info
	}
	averagePrice = averagePrice.Div(types.NewCurrency64(uint64(len(hosts))))
	// HACK: 4320 is one month, and 1024^3 is a GB. Price is reported as per GB
	// per month.
	estimatedCost := averagePrice.Mul(types.NewCurrency64(4320)).Mul(types.NewCurrency64(1024 * 1024 * 1024))
	info.Competition = estimatedCost

	return info
}

// Close saves the state of the Gateway and stops its listener process.
func (h *Host) Close() error {
	id := h.mu.RLock()
	// save the latest host state
	if err := h.save(); err != nil {
		return err
	}
	h.mu.RUnlock(id)
	// clear the port mapping (no effect if UPnP not supported)
	h.clearPort(h.myAddr.Port())
	// shut down the listener
	return h.listener.Close()
}