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 }