github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/cmd/siac/hostcmd.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"text/tabwriter"
    10  
    11  	"github.com/Synthesix/Sia/modules"
    12  	"github.com/Synthesix/Sia/node/api"
    13  	"github.com/Synthesix/Sia/types"
    14  
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  var (
    19  	hostAnnounceCmd = &cobra.Command{
    20  		Use:   "announce",
    21  		Short: "Announce yourself as a host",
    22  		Long: `Announce yourself as a host on the network.
    23  Announcing will also configure the host to start accepting contracts.
    24  You can revert this by running:
    25  	siac host config acceptingcontracts false
    26  You may also supply a specific address to be announced, e.g.:
    27  	siac host announce my-host-domain.com:9001
    28  Doing so will override the standard connectivity checks.`,
    29  		Run: hostannouncecmd,
    30  	}
    31  
    32  	hostCmd = &cobra.Command{
    33  		Use:   "host",
    34  		Short: "Perform host actions",
    35  		Long:  "View or modify host settings.",
    36  		Run:   wrap(hostcmd),
    37  	}
    38  
    39  	hostConfigCmd = &cobra.Command{
    40  		Use:   "config [setting] [value]",
    41  		Short: "Modify host settings",
    42  		Long: `Modify host settings.
    43  
    44  Available settings:
    45       acceptingcontracts:   boolean
    46       maxduration:          blocks
    47       maxdownloadbatchsize: bytes
    48       maxrevisebatchsize:   bytes
    49       netaddress:           string
    50       windowsize:           blocks
    51  
    52       collateral:       currency
    53       collateralbudget: currency
    54       maxcollateral:    currency
    55  
    56       mincontractprice:          currency
    57       mindownloadbandwidthprice: currency / TB
    58       minstorageprice:           currency / TB / Month
    59       minuploadbandwidthprice:   currency / TB
    60  
    61  Currency units can be specified, e.g. 10SC; run 'siac help wallet' for details.
    62  
    63  Durations (maxduration and windowsize) must be specified in either blocks (b),
    64  hours (h), days (d), or weeks (w). A block is approximately 10 minutes, so one
    65  hour is six blocks, a day is 144 blocks, and a week is 1008 blocks.
    66  
    67  For a description of each parameter, see doc/API.md.
    68  
    69  To configure the host to accept new contracts, set acceptingcontracts to true:
    70  	siac host config acceptingcontracts true
    71  `,
    72  		Run: wrap(hostconfigcmd),
    73  	}
    74  
    75  	hostFolderAddCmd = &cobra.Command{
    76  		Use:   "add [path] [size]",
    77  		Short: "Add a storage folder to the host",
    78  		Long:  "Add a storage folder to the host, specifying how much data it should store",
    79  		Run:   wrap(hostfolderaddcmd),
    80  	}
    81  
    82  	hostFolderCmd = &cobra.Command{
    83  		Use:   "folder",
    84  		Short: "Add, remove, or resize a storage folder",
    85  		Long:  "Add, remove, or resize a storage folder.",
    86  	}
    87  
    88  	hostFolderRemoveCmd = &cobra.Command{
    89  		Use:   "remove [path]",
    90  		Short: "Remove a storage folder from the host",
    91  		Long: `Remove a storage folder from the host. Note that this does not delete any
    92  data; it will instead be distributed across the remaining storage folders.`,
    93  
    94  		Run: wrap(hostfolderremovecmd),
    95  	}
    96  
    97  	hostFolderResizeCmd = &cobra.Command{
    98  		Use:   "resize [path] [size]",
    99  		Short: "Resize a storage folder",
   100  		Long: `Change how much data a storage folder should store. If the new size is less
   101  than what the folder is currently storing, data will be distributed across the
   102  other storage folders.`,
   103  		Run: wrap(hostfolderresizecmd),
   104  	}
   105  
   106  	hostSectorCmd = &cobra.Command{
   107  		Use:   "sector",
   108  		Short: "Add or delete a sector (add not supported)",
   109  		Long: `Add or delete a sector. Adding is not currently supported. Note that
   110  deleting a sector may impact host revenue.`,
   111  	}
   112  
   113  	hostSectorDeleteCmd = &cobra.Command{
   114  		Use:   "delete [root]",
   115  		Short: "Delete a sector",
   116  		Long: `Delete a sector, identified by its Merkle root. Note that deleting a
   117  sector may impact host revenue.`,
   118  		Run: wrap(hostsectordeletecmd),
   119  	}
   120  )
   121  
   122  // hostcmd is the handler for the command `siac host`.
   123  // Prints info about the host and its storage folders.
   124  func hostcmd() {
   125  	hg := new(api.HostGET)
   126  	err := getAPI("/host", hg)
   127  	if err != nil {
   128  		die("Could not fetch host settings:", err)
   129  	}
   130  	sg := new(api.StorageGET)
   131  	err = getAPI("/host/storage", sg)
   132  	if err != nil {
   133  		die("Could not fetch storage info:", err)
   134  	}
   135  
   136  	es := hg.ExternalSettings
   137  	fm := hg.FinancialMetrics
   138  	is := hg.InternalSettings
   139  	nm := hg.NetworkMetrics
   140  
   141  	// calculate total storage available and remaining
   142  	var totalstorage, storageremaining uint64
   143  	for _, folder := range sg.Folders {
   144  		totalstorage += folder.Capacity
   145  		storageremaining += folder.CapacityRemaining
   146  	}
   147  
   148  	// convert price from bytes/block to TB/Month
   149  	price := currencyUnits(is.MinStoragePrice.Mul(modules.BlockBytesPerMonthTerabyte))
   150  	// calculate total revenue
   151  	totalRevenue := fm.ContractCompensation.
   152  		Add(fm.StorageRevenue).
   153  		Add(fm.DownloadBandwidthRevenue).
   154  		Add(fm.UploadBandwidthRevenue)
   155  	totalPotentialRevenue := fm.PotentialContractCompensation.
   156  		Add(fm.PotentialStorageRevenue).
   157  		Add(fm.PotentialDownloadBandwidthRevenue).
   158  		Add(fm.PotentialUploadBandwidthRevenue)
   159  	// determine the display method for the net address.
   160  	netaddr := es.NetAddress
   161  	if is.NetAddress == "" {
   162  		netaddr += " (automatically determined)"
   163  	} else {
   164  		netaddr += " (manually specified)"
   165  	}
   166  
   167  	var connectabilityString string
   168  	if hg.WorkingStatus == "working" {
   169  		connectabilityString = "Host appears to be working."
   170  	} else if hg.WorkingStatus == "not working" && hg.ConnectabilityStatus == "connectable" {
   171  		connectabilityString = "Nobody is connecting to host. Try re-announcing."
   172  	} else if hg.WorkingStatus == "checking" || hg.ConnectabilityStatus == "checking" {
   173  		connectabilityString = "Host is checking status (takes a few minues)."
   174  	} else {
   175  		connectabilityString = "Host is not connectable (re-checks every few minutes)."
   176  	}
   177  
   178  	if hostVerbose {
   179  		// describe net address
   180  		fmt.Printf(`General Info:
   181  	Connectability Status: %v
   182  
   183  Host Internal Settings:
   184  	acceptingcontracts:   %v
   185  	maxduration:          %v Weeks
   186  	maxdownloadbatchsize: %v
   187  	maxrevisebatchsize:   %v
   188  	netaddress:           %v
   189  	windowsize:           %v Hours
   190  
   191  	collateral:       %v / TB / Month
   192  	collateralbudget: %v
   193  	maxcollateral:    %v Per Contract
   194  
   195  	mincontractprice:          %v
   196  	mindownloadbandwidthprice: %v / TB
   197  	minstorageprice:           %v / TB / Month
   198  	minuploadbandwidthprice:   %v / TB
   199  
   200  Host Financials:
   201  	Contract Count:               %v
   202  	Transaction Fee Compensation: %v
   203  	Potential Fee Compensation:   %v
   204  	Transaction Fee Expenses:     %v
   205  
   206  	Storage Revenue:           %v
   207  	Potential Storage Revenue: %v
   208  
   209  	Locked Collateral: %v
   210  	Risked Collateral: %v
   211  	Lost Collateral:   %v
   212  
   213  	Download Revenue:           %v
   214  	Potential Download Revenue: %v
   215  	Upload Revenue:             %v
   216  	Potential Upload Revenue:   %v
   217  
   218  RPC Stats:
   219  	Error Calls:        %v
   220  	Unrecognized Calls: %v
   221  	Download Calls:     %v
   222  	Renew Calls:        %v
   223  	Revise Calls:       %v
   224  	Settings Calls:     %v
   225  	FormContract Calls: %v
   226  `,
   227  			connectabilityString,
   228  
   229  			yesNo(is.AcceptingContracts), periodUnits(is.MaxDuration),
   230  			filesizeUnits(int64(is.MaxDownloadBatchSize)),
   231  			filesizeUnits(int64(is.MaxReviseBatchSize)), netaddr,
   232  			is.WindowSize/6,
   233  
   234  			currencyUnits(is.Collateral.Mul(modules.BlockBytesPerMonthTerabyte)),
   235  			currencyUnits(is.CollateralBudget),
   236  			currencyUnits(is.MaxCollateral),
   237  
   238  			currencyUnits(is.MinContractPrice),
   239  			currencyUnits(is.MinDownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)),
   240  			currencyUnits(is.MinStoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)),
   241  			currencyUnits(is.MinUploadBandwidthPrice.Mul(modules.BytesPerTerabyte)),
   242  
   243  			fm.ContractCount, currencyUnits(fm.ContractCompensation),
   244  			currencyUnits(fm.PotentialContractCompensation),
   245  			currencyUnits(fm.TransactionFeeExpenses),
   246  
   247  			currencyUnits(fm.StorageRevenue),
   248  			currencyUnits(fm.PotentialStorageRevenue),
   249  
   250  			currencyUnits(fm.LockedStorageCollateral),
   251  			currencyUnits(fm.RiskedStorageCollateral),
   252  			currencyUnits(fm.LostStorageCollateral),
   253  
   254  			currencyUnits(fm.DownloadBandwidthRevenue),
   255  			currencyUnits(fm.PotentialDownloadBandwidthRevenue),
   256  			currencyUnits(fm.UploadBandwidthRevenue),
   257  			currencyUnits(fm.PotentialUploadBandwidthRevenue),
   258  
   259  			nm.ErrorCalls, nm.UnrecognizedCalls, nm.DownloadCalls,
   260  			nm.RenewCalls, nm.ReviseCalls, nm.SettingsCalls,
   261  			nm.FormContractCalls)
   262  	} else {
   263  		fmt.Printf(`Host info:
   264  	Connectability Status: %v
   265  
   266  	Storage:      %v (%v used)
   267  	Price:        %v / TB / Month
   268  	Max Duration: %v Weeks
   269  
   270  	Accepting Contracts:  %v
   271  	Anticipated Revenue:  %v
   272  	Locked Collateral:    %v
   273  	Revenue:              %v
   274  `,
   275  			connectabilityString,
   276  
   277  			filesizeUnits(int64(totalstorage)),
   278  			filesizeUnits(int64(totalstorage-storageremaining)), price,
   279  			periodUnits(is.MaxDuration),
   280  
   281  			yesNo(is.AcceptingContracts), currencyUnits(totalPotentialRevenue),
   282  			currencyUnits(fm.LockedStorageCollateral),
   283  			currencyUnits(totalRevenue))
   284  	}
   285  
   286  	// if wallet is locked print warning
   287  	walletstatus := new(api.WalletGET)
   288  	walleterr := getAPI("/wallet", walletstatus)
   289  	if walleterr != nil {
   290  		fmt.Print("\nWarning:\n	Could not get wallet status. A working wallet is needed in order to operate your host. Error: ")
   291  		fmt.Println(walleterr)
   292  	} else if !walletstatus.Unlocked {
   293  		fmt.Println("\nWarning:\n	Your wallet is locked. You must unlock your wallet for the host to function properly.")
   294  	}
   295  
   296  	fmt.Println("\nStorage Folders:")
   297  
   298  	// display storage folder info
   299  	sort.Slice(sg.Folders, func(i, j int) bool {
   300  		return sg.Folders[i].Path < sg.Folders[j].Path
   301  	})
   302  	if len(sg.Folders) == 0 {
   303  		fmt.Println("No storage folders configured")
   304  		return
   305  	}
   306  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
   307  	fmt.Fprintf(w, "\tUsed\tCapacity\t%% Used\tPath\n")
   308  	for _, folder := range sg.Folders {
   309  		curSize := int64(folder.Capacity - folder.CapacityRemaining)
   310  		pctUsed := 100 * (float64(curSize) / float64(folder.Capacity))
   311  		fmt.Fprintf(w, "\t%s\t%s\t%.2f\t%s\n", filesizeUnits(curSize), filesizeUnits(int64(folder.Capacity)), pctUsed, folder.Path)
   312  	}
   313  	w.Flush()
   314  }
   315  
   316  // hostconfigcmd is the handler for the command `siac host config [setting] [value]`.
   317  // Modifies host settings.
   318  func hostconfigcmd(param, value string) {
   319  	var err error
   320  	switch param {
   321  	// currency (convert to hastings)
   322  	case "collateralbudget", "maxcollateral", "mincontractprice":
   323  		value, err = parseCurrency(value)
   324  		if err != nil {
   325  			die("Could not parse "+param+":", err)
   326  		}
   327  
   328  	// currency/TB (convert to hastings/byte)
   329  	case "mindownloadbandwidthprice", "minuploadbandwidthprice":
   330  		hastings, err := parseCurrency(value)
   331  		if err != nil {
   332  			die("Could not parse "+param+":", err)
   333  		}
   334  		i, _ := new(big.Int).SetString(hastings, 10)
   335  		c := types.NewCurrency(i).Div(modules.BytesPerTerabyte)
   336  		value = c.String()
   337  
   338  	// currency/TB/month (convert to hastings/byte/block)
   339  	case "collateral", "minstorageprice":
   340  		hastings, err := parseCurrency(value)
   341  		if err != nil {
   342  			die("Could not parse "+param+":", err)
   343  		}
   344  		i, _ := new(big.Int).SetString(hastings, 10)
   345  		c := types.NewCurrency(i).Div(modules.BlockBytesPerMonthTerabyte)
   346  		value = c.String()
   347  
   348  	// bool (allow "yes" and "no")
   349  	case "acceptingcontracts":
   350  		switch strings.ToLower(value) {
   351  		case "yes":
   352  			value = "true"
   353  		case "no":
   354  			value = "false"
   355  		}
   356  
   357  	// duration (convert to blocks)
   358  	case "maxduration", "windowsize":
   359  		value, err = parsePeriod(value)
   360  		if err != nil {
   361  			die("Could not parse "+param+":", err)
   362  		}
   363  
   364  	// other valid settings
   365  	case "maxdownloadbatchsize", "maxrevisebatchsize", "netaddress":
   366  
   367  	// invalid settings
   368  	default:
   369  		die("\"" + param + "\" is not a host setting")
   370  	}
   371  	err = post("/host", param+"="+value)
   372  	if err != nil {
   373  		die("Could not update host settings:", err)
   374  	}
   375  	fmt.Println("Host settings updated.")
   376  
   377  	// get the estimated conversion rate.
   378  	var eg api.HostEstimateScoreGET
   379  	err = getAPI(fmt.Sprintf("/host/estimatescore?%v=%v", param, value), &eg)
   380  	if err != nil {
   381  		if err.Error() == "cannot call /host/estimatescore without the renter module" {
   382  			// score estimate requires the renter module
   383  			return
   384  		}
   385  		die("could not get host score estimate:", err)
   386  	}
   387  	fmt.Printf("Estimated conversion rate: %v%%\n", eg.ConversionRate)
   388  }
   389  
   390  // hostannouncecmd is the handler for the command `siac host announce`.
   391  // Announces yourself as a host to the network. Optionally takes an address to
   392  // announce as.
   393  func hostannouncecmd(cmd *cobra.Command, args []string) {
   394  	var err error
   395  	switch len(args) {
   396  	case 0:
   397  		err = post("/host/announce", "")
   398  	case 1:
   399  		err = post("/host/announce", "netaddress="+args[0])
   400  	default:
   401  		cmd.UsageFunc()(cmd)
   402  		os.Exit(exitCodeUsage)
   403  	}
   404  	if err != nil {
   405  		die("Could not announce host:", err)
   406  	}
   407  	fmt.Println("Host announcement submitted to network.")
   408  
   409  	// start accepting contracts
   410  	err = post("/host", "acceptingcontracts=true")
   411  	if err != nil {
   412  		die("Could not configure host to accept contracts:", err)
   413  	}
   414  	fmt.Println(`The host has also been configured to accept contracts.
   415  To revert this, run:
   416  	siac host config acceptingcontracts false`)
   417  }
   418  
   419  // hostfolderaddcmd adds a folder to the host.
   420  func hostfolderaddcmd(path, size string) {
   421  	size, err := parseFilesize(size)
   422  	if err != nil {
   423  		die("Could not parse size:", err)
   424  	}
   425  	// round size down to nearest multiple of 256MiB
   426  	var sizeUint64 uint64
   427  	fmt.Sscan(size, &sizeUint64)
   428  	sizeUint64 /= 64 * modules.SectorSize
   429  	sizeUint64 *= 64 * modules.SectorSize
   430  	size = fmt.Sprint(sizeUint64)
   431  
   432  	err = post("/host/storage/folders/add", fmt.Sprintf("path=%s&size=%s", abs(path), size))
   433  	if err != nil {
   434  		die("Could not add folder:", err)
   435  	}
   436  	fmt.Println("Added folder", path)
   437  }
   438  
   439  // hostfolderremovecmd removes a folder from the host.
   440  func hostfolderremovecmd(path string) {
   441  	err := post("/host/storage/folders/remove", "path="+abs(path))
   442  	if err != nil {
   443  		die("Could not remove folder:", err)
   444  	}
   445  	fmt.Println("Removed folder", path)
   446  }
   447  
   448  // hostfolderresizecmd resizes a folder in the host.
   449  func hostfolderresizecmd(path, newsize string) {
   450  	newsize, err := parseFilesize(newsize)
   451  	if err != nil {
   452  		die("Could not parse size:", err)
   453  	}
   454  	// round size down to nearest multiple of 256MiB
   455  	var sizeUint64 uint64
   456  	fmt.Sscan(newsize, &sizeUint64)
   457  	sizeUint64 /= 64 * modules.SectorSize
   458  	sizeUint64 *= 64 * modules.SectorSize
   459  	newsize = fmt.Sprint(sizeUint64)
   460  
   461  	err = post("/host/storage/folders/resize", fmt.Sprintf("path=%s&newsize=%s", abs(path), newsize))
   462  	if err != nil {
   463  		die("Could not resize folder:", err)
   464  	}
   465  	fmt.Printf("Resized folder %v to %v\n", path, newsize)
   466  }
   467  
   468  // hostsectordeletecmd deletes a sector from the host.
   469  func hostsectordeletecmd(root string) {
   470  	err := post("/host/storage/sectors/delete/"+root, "")
   471  	if err != nil {
   472  		die("Could not delete sector:", err)
   473  	}
   474  	fmt.Println("Deleted sector", root)
   475  }