gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/host.go (about) 1 package api 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "gitlab.com/SiaPrime/SiaPrime/build" 9 "gitlab.com/SiaPrime/SiaPrime/modules" 10 "gitlab.com/SiaPrime/SiaPrime/types" 11 12 "github.com/julienschmidt/httprouter" 13 ) 14 15 var ( 16 // errNoPath is returned when a call fails to provide a nonempty string 17 // for the path parameter. 18 errNoPath = Error{"path parameter is required"} 19 20 // errStorageFolderNotFound is returned if a call is made looking for a 21 // storage folder which does not appear to exist within the storage 22 // manager. 23 errStorageFolderNotFound = errors.New("storage folder with the provided path could not be found") 24 ) 25 26 type ( 27 // ContractInfoGET contains the information that is returned after a GET request 28 // to /host/contracts - information for the host about stored obligations. 29 ContractInfoGET struct { 30 Contracts []modules.StorageObligation `json:"contracts"` 31 } 32 33 // HostGET contains the information that is returned after a GET request to 34 // /host - a bunch of information about the status of the host. 35 HostGET struct { 36 ExternalSettings modules.HostExternalSettings `json:"externalsettings"` 37 FinancialMetrics modules.HostFinancialMetrics `json:"financialmetrics"` 38 InternalSettings modules.HostInternalSettings `json:"internalsettings"` 39 NetworkMetrics modules.HostNetworkMetrics `json:"networkmetrics"` 40 ConnectabilityStatus modules.HostConnectabilityStatus `json:"connectabilitystatus"` 41 WorkingStatus modules.HostWorkingStatus `json:"workingstatus"` 42 } 43 44 // HostEstimateScoreGET contains the information that is returned from a 45 // /host/estimatescore call. 46 HostEstimateScoreGET struct { 47 EstimatedScore types.Currency `json:"estimatedscore"` 48 ConversionRate float64 `json:"conversionrate"` 49 } 50 51 // StorageGET contains the information that is returned after a GET request 52 // to /host/storage - a bunch of information about the status of storage 53 // management on the host. 54 StorageGET struct { 55 Folders []modules.StorageFolderMetadata `json:"folders"` 56 } 57 ) 58 59 // folderIndex determines the index of the storage folder with the provided 60 // path. 61 func folderIndex(folderPath string, storageFolders []modules.StorageFolderMetadata) (int, error) { 62 for _, sf := range storageFolders { 63 if sf.Path == folderPath { 64 return int(sf.Index), nil 65 } 66 } 67 return -1, errStorageFolderNotFound 68 } 69 70 // hostContractInfoHandler handles the API call to get the contract information of the host. 71 // Information is retrieved via the storage obligations from the host database. 72 func (api *API) hostContractInfoHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 73 cg := ContractInfoGET{ 74 Contracts: api.host.StorageObligations(), 75 } 76 WriteJSON(w, cg) 77 } 78 79 // hostHandlerGET handles GET requests to the /host API endpoint, returning key 80 // information about the host. 81 func (api *API) hostHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 82 es := api.host.ExternalSettings() 83 fm := api.host.FinancialMetrics() 84 is := api.host.InternalSettings() 85 nm := api.host.NetworkMetrics() 86 cs := api.host.ConnectabilityStatus() 87 ws := api.host.WorkingStatus() 88 hg := HostGET{ 89 ExternalSettings: es, 90 FinancialMetrics: fm, 91 InternalSettings: is, 92 NetworkMetrics: nm, 93 ConnectabilityStatus: cs, 94 WorkingStatus: ws, 95 } 96 WriteJSON(w, hg) 97 } 98 99 // parseHostSettings a request's query strings and returns a 100 // modules.HostInternalSettings configured with the request's query string 101 // parameters. 102 func (api *API) parseHostSettings(req *http.Request) (modules.HostInternalSettings, error) { 103 settings := api.host.InternalSettings() 104 105 if req.FormValue("acceptingcontracts") != "" { 106 var x bool 107 _, err := fmt.Sscan(req.FormValue("acceptingcontracts"), &x) 108 if err != nil { 109 return modules.HostInternalSettings{}, err 110 } 111 settings.AcceptingContracts = x 112 } 113 if req.FormValue("maxdownloadbatchsize") != "" { 114 var x uint64 115 _, err := fmt.Sscan(req.FormValue("maxdownloadbatchsize"), &x) 116 if err != nil { 117 return modules.HostInternalSettings{}, err 118 } 119 settings.MaxDownloadBatchSize = x 120 } 121 if req.FormValue("maxduration") != "" { 122 var x types.BlockHeight 123 _, err := fmt.Sscan(req.FormValue("maxduration"), &x) 124 if err != nil { 125 return modules.HostInternalSettings{}, err 126 } 127 settings.MaxDuration = x 128 } 129 if req.FormValue("maxrevisebatchsize") != "" { 130 var x uint64 131 _, err := fmt.Sscan(req.FormValue("maxrevisebatchsize"), &x) 132 if err != nil { 133 return modules.HostInternalSettings{}, err 134 } 135 settings.MaxReviseBatchSize = x 136 } 137 if req.FormValue("netaddress") != "" { 138 var x modules.NetAddress 139 _, err := fmt.Sscan(req.FormValue("netaddress"), &x) 140 if err != nil { 141 return modules.HostInternalSettings{}, err 142 } 143 settings.NetAddress = x 144 } 145 if req.FormValue("windowsize") != "" { 146 var x types.BlockHeight 147 _, err := fmt.Sscan(req.FormValue("windowsize"), &x) 148 if err != nil { 149 return modules.HostInternalSettings{}, err 150 } 151 settings.WindowSize = x 152 } 153 154 if req.FormValue("collateral") != "" { 155 var x types.Currency 156 _, err := fmt.Sscan(req.FormValue("collateral"), &x) 157 if err != nil { 158 return modules.HostInternalSettings{}, err 159 } 160 settings.Collateral = x 161 } 162 if req.FormValue("collateralbudget") != "" { 163 var x types.Currency 164 _, err := fmt.Sscan(req.FormValue("collateralbudget"), &x) 165 if err != nil { 166 return modules.HostInternalSettings{}, err 167 } 168 settings.CollateralBudget = x 169 } 170 if req.FormValue("maxcollateral") != "" { 171 var x types.Currency 172 _, err := fmt.Sscan(req.FormValue("maxcollateral"), &x) 173 if err != nil { 174 return modules.HostInternalSettings{}, err 175 } 176 settings.MaxCollateral = x 177 } 178 179 if req.FormValue("minbaserpcprice") != "" { 180 var x types.Currency 181 _, err := fmt.Sscan(req.FormValue("minbaserpcprice"), &x) 182 if err != nil { 183 return modules.HostInternalSettings{}, err 184 } 185 settings.MinBaseRPCPrice = x 186 } 187 if req.FormValue("mincontractprice") != "" { 188 var x types.Currency 189 _, err := fmt.Sscan(req.FormValue("mincontractprice"), &x) 190 if err != nil { 191 return modules.HostInternalSettings{}, err 192 } 193 settings.MinContractPrice = x 194 } 195 if req.FormValue("mindownloadbandwidthprice") != "" { 196 var x types.Currency 197 _, err := fmt.Sscan(req.FormValue("mindownloadbandwidthprice"), &x) 198 if err != nil { 199 return modules.HostInternalSettings{}, err 200 } 201 settings.MinDownloadBandwidthPrice = x 202 } 203 if req.FormValue("minsectoraccessprice") != "" { 204 var x types.Currency 205 _, err := fmt.Sscan(req.FormValue("minsectoraccessprice"), &x) 206 if err != nil { 207 return modules.HostInternalSettings{}, err 208 } 209 settings.MinSectorAccessPrice = x 210 } 211 if req.FormValue("minstorageprice") != "" { 212 var x types.Currency 213 _, err := fmt.Sscan(req.FormValue("minstorageprice"), &x) 214 if err != nil { 215 return modules.HostInternalSettings{}, err 216 } 217 settings.MinStoragePrice = x 218 } 219 if req.FormValue("minuploadbandwidthprice") != "" { 220 var x types.Currency 221 _, err := fmt.Sscan(req.FormValue("minuploadbandwidthprice"), &x) 222 if err != nil { 223 return modules.HostInternalSettings{}, err 224 } 225 settings.MinUploadBandwidthPrice = x 226 } 227 228 return settings, nil 229 } 230 231 // hostEstimateScoreGET handles the POST request to /host/estimatescore and 232 // computes an estimated HostDB score for the provided settings. 233 func (api *API) hostEstimateScoreGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 234 // This call requires a renter, check that it is present. 235 if api.renter == nil { 236 WriteError(w, Error{"cannot call /host/estimatescore without the renter module"}, http.StatusBadRequest) 237 return 238 } 239 240 settings, err := api.parseHostSettings(req) 241 if err != nil { 242 WriteError(w, Error{"error parsing host settings: " + err.Error()}, http.StatusBadRequest) 243 return 244 } 245 var totalStorage, remainingStorage uint64 246 for _, sf := range api.host.StorageFolders() { 247 totalStorage += sf.Capacity 248 remainingStorage += sf.CapacityRemaining 249 } 250 mergedSettings := modules.HostExternalSettings{ 251 AcceptingContracts: settings.AcceptingContracts, 252 MaxDownloadBatchSize: settings.MaxDownloadBatchSize, 253 MaxDuration: settings.MaxDuration, 254 MaxReviseBatchSize: settings.MaxReviseBatchSize, 255 RemainingStorage: remainingStorage, 256 SectorSize: modules.SectorSize, 257 TotalStorage: totalStorage, 258 WindowSize: settings.WindowSize, 259 260 Collateral: settings.Collateral, 261 MaxCollateral: settings.MaxCollateral, 262 263 ContractPrice: settings.MinContractPrice, 264 DownloadBandwidthPrice: settings.MinDownloadBandwidthPrice, 265 StoragePrice: settings.MinStoragePrice, 266 UploadBandwidthPrice: settings.MinUploadBandwidthPrice, 267 268 Version: build.Version, 269 } 270 entry := modules.HostDBEntry{} 271 entry.PublicKey = api.host.PublicKey() 272 entry.HostExternalSettings = mergedSettings 273 // Use the default allowance for now, since we do not know what sort of 274 // allowance the renters may use to attempt to access this host. 275 estimatedScoreBreakdown, err := api.renter.EstimateHostScore(entry, modules.DefaultAllowance) 276 if err != nil { 277 WriteError(w, Error{"error estimating host score: " + err.Error()}, http.StatusInternalServerError) 278 return 279 } 280 e := HostEstimateScoreGET{ 281 EstimatedScore: estimatedScoreBreakdown.Score, 282 ConversionRate: estimatedScoreBreakdown.ConversionRate, 283 } 284 WriteJSON(w, e) 285 } 286 287 // hostHandlerPOST handles POST request to the /host API endpoint, which sets 288 // the internal settings of the host. 289 func (api *API) hostHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 290 settings, err := api.parseHostSettings(req) 291 if err != nil { 292 WriteError(w, Error{"error parsing host settings: " + err.Error()}, http.StatusBadRequest) 293 return 294 } 295 296 err = api.host.SetInternalSettings(settings) 297 if err != nil { 298 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 299 return 300 } 301 WriteSuccess(w) 302 } 303 304 // hostAnnounceHandler handles the API call to get the host to announce itself 305 // to the network. 306 func (api *API) hostAnnounceHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 307 var err error 308 if addr := req.FormValue("netaddress"); addr != "" { 309 err = api.host.AnnounceAddress(modules.NetAddress(addr)) 310 } else { 311 err = api.host.Announce() 312 } 313 if err != nil { 314 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 315 return 316 } 317 WriteSuccess(w) 318 } 319 320 // storageHandler returns a bunch of information about storage management on 321 // the host. 322 func (api *API) storageHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 323 WriteJSON(w, StorageGET{ 324 Folders: api.host.StorageFolders(), 325 }) 326 } 327 328 // storageFoldersAddHandler adds a storage folder to the storage manager. 329 func (api *API) storageFoldersAddHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 330 folderPath := req.FormValue("path") 331 var folderSize uint64 332 _, err := fmt.Sscan(req.FormValue("size"), &folderSize) 333 if err != nil { 334 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 335 return 336 } 337 err = api.host.AddStorageFolder(folderPath, folderSize) 338 if err != nil { 339 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 340 return 341 } 342 WriteSuccess(w) 343 } 344 345 // storageFoldersResizeHandler resizes a storage folder in the storage manager. 346 func (api *API) storageFoldersResizeHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 347 folderPath := req.FormValue("path") 348 if folderPath == "" { 349 WriteError(w, Error{"path parameter is required"}, http.StatusBadRequest) 350 return 351 } 352 353 storageFolders := api.host.StorageFolders() 354 folderIndex, err := folderIndex(folderPath, storageFolders) 355 if err != nil { 356 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 357 return 358 } 359 360 var newSize uint64 361 _, err = fmt.Sscan(req.FormValue("newsize"), &newSize) 362 if err != nil { 363 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 364 return 365 } 366 err = api.host.ResizeStorageFolder(uint16(folderIndex), newSize, false) 367 if err != nil { 368 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 369 return 370 } 371 WriteSuccess(w) 372 } 373 374 // storageFoldersRemoveHandler removes a storage folder from the storage 375 // manager. 376 func (api *API) storageFoldersRemoveHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 377 folderPath := req.FormValue("path") 378 if folderPath == "" { 379 WriteError(w, Error{"path parameter is required"}, http.StatusBadRequest) 380 return 381 } 382 383 storageFolders := api.host.StorageFolders() 384 folderIndex, err := folderIndex(folderPath, storageFolders) 385 if err != nil { 386 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 387 return 388 } 389 390 force := req.FormValue("force") == "true" 391 err = api.host.RemoveStorageFolder(uint16(folderIndex), force) 392 if err != nil { 393 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 394 return 395 } 396 WriteSuccess(w) 397 } 398 399 // storageSectorsDeleteHandler handles the call to delete a sector from the 400 // storage manager. 401 func (api *API) storageSectorsDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 402 sectorRoot, err := scanHash(ps.ByName("merkleroot")) 403 if err != nil { 404 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 405 return 406 } 407 err = api.host.DeleteSector(sectorRoot) 408 if err != nil { 409 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 410 return 411 } 412 WriteSuccess(w) 413 }