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  }