gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/daemon.go (about)

     1  package api
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"math/big"
    12  	"net/http"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  
    17  	"github.com/inconshreveable/go-update"
    18  	"github.com/julienschmidt/httprouter"
    19  	"github.com/kardianos/osext"
    20  
    21  	"gitlab.com/SiaPrime/SiaPrime/build"
    22  	"gitlab.com/SiaPrime/SiaPrime/modules"
    23  	"gitlab.com/SiaPrime/SiaPrime/types"
    24  )
    25  
    26  const (
    27  	// The developer key is used to sign updates and other important Sia-
    28  	// related information.
    29  	developerKey = `-----BEGIN PUBLIC KEY-----
    30  MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEAsoQHOEU6s/EqMDtw5HvA
    31  YPTUaBgnviMFbG3bMsRqSCD8ug4XJYh+Ik6WP0xgq+OPDehPiaXK8ghAtBiW1EJK
    32  mBRwlABXAzREZg8wRfG4l8Zj6ckAPJOgLn0jobXy6/SCQ+jZSWh4Y8DYr+LA3Mn3
    33  EOga7Jvhpc3fTZ232GBGJ1BobuNfRfYmwxSphv+T4vzIA3JUjVfa8pYZGIjh5XbJ
    34  5M8Lef0Xa9eqr6lYm5kQoOIXeOW56ImqI2BKg/I9NGw9phSPbwaFfy1V2kfHp5Xy
    35  DtKnyj/O9zDi+qUKjoIivnEoV+3DkioHUWv7Fpf7yx/9cPyckwvaBsTd9Cfp4uBx
    36  qJ5Qyv69VZQiD6DikNwgzjGbIjiLwfTObhInKZUoYl48yzgkR80ja5TW0SoidNvO
    37  4WTbWcLolOl522VarTs7wlgbq0Ad7yrNVnHzo447v2iT20ILH2oeAcZqvpcvRmTl
    38  U6uKoaVmBH3D3Y19dPluOjK53BrqfQ5L8RFli2wEJktPsi5fUTd4UI9BgnUieuDz
    39  S7h/VH9bv9ZVvyjpu/uVjdvaikT3zbIy9J6wS6uE5qPLPhI4B9HgbrQ03muDGpql
    40  gZrMiL3GdYrBiqpIbaWHfM0eMWEK3ZScUdtCgUXMMrkvaUJ4g9wEgbONFVVOMIV+
    41  YubIuzBFqug6WyxN/EAM/6Fss832AwVPcYM0NDTVGVdVplLMdN8YNjrYuaPngBCG
    42  e8QaTWtHzLujyBIkVdAHqfkRS65jp7JLLMx7jUA74/E/v+0cNew3Y1p2gt3iQH8t
    43  w93xn9IPUfQympc4h3KerP/Yn6P/qAh68jQkOiMMS+VbCq/BOn8Q3GbR+8rQ8dmk
    44  qVoGA7XrPQ6bymKBTghk2Ek+ZjxrpAoj0xYoYyzWf0kuxeOT8kAjlLLmfQ8pm75S
    45  QHLqH49FyfeETIU02rkw2oMOX/EYdJzZukHuouwbpKSElpRx+xTnaSemMJo+U7oX
    46  xVjma3Zynh9w12abnFWkZKtrxwXv7FCSzb0UZmMWUqWzCS03Rrlur21jp4q2Wl71
    47  Vt92xe5YbC/jbh386F1e/qGq6p+D1AmBynIpp/HE6fPsc9LWgJDDkREZcp7hthGW
    48  IdYPeP3CesFHnsZMueZRib0i7lNUkBSRneO1y/C9poNv1vOeTCNEE0jvhp/XOJuc
    49  yCQtrUSNALsvm7F+bnwP2F7K34k7MOlOgnTGqCqW+9WwBcjR44B0HI+YERCcRmJ8
    50  krBuVo9OBMV0cYBWpjo3UI9j3lHESCYhLnCz7SPap7C1yORc2ydJh+qjKqdLBHom
    51  t+JydcdJLbIG+kb3jB9QIIu5A4TlSGlHV6ewtxIWLS1473jEkITiVTt0Y5k+VLfW
    52  bwIDAQAB
    53  -----END PUBLIC KEY-----`
    54  )
    55  
    56  type (
    57  	// DaemonVersionGet contains information about the running daemon's version.
    58  	DaemonVersionGet struct {
    59  		Version     string
    60  		GitRevision string
    61  		BuildTime   string
    62  	}
    63  
    64  	// DaemonUpdateGet contains information about a potential available update for
    65  	// the daemon.
    66  	DaemonUpdateGet struct {
    67  		Available bool   `json:"available"`
    68  		Version   string `json:"version"`
    69  	}
    70  
    71  	// UpdateInfo indicates whether an update is available, and to what
    72  	// version.
    73  	UpdateInfo struct {
    74  		Available bool   `json:"available"`
    75  		Version   string `json:"version"`
    76  	}
    77  	// gitlabRelease represents some of the JSON returned by the GitLab
    78  	// release API endpoint. Only the fields relevant to updating are
    79  	// included.
    80  	gitlabRelease struct {
    81  		TagName string `json:"name"`
    82  	}
    83  
    84  	// SiaConstants is a struct listing all of the constants in use.
    85  	SiaConstants struct {
    86  		BlockFrequency         types.BlockHeight `json:"blockfrequency"`
    87  		BlockSizeLimit         uint64            `json:"blocksizelimit"`
    88  		ExtremeFutureThreshold types.Timestamp   `json:"extremefuturethreshold"`
    89  		FutureThreshold        types.Timestamp   `json:"futurethreshold"`
    90  		GenesisTimestamp       types.Timestamp   `json:"genesistimestamp"`
    91  		MaturityDelay          types.BlockHeight `json:"maturitydelay"`
    92  		MedianTimestampWindow  uint64            `json:"mediantimestampwindow"`
    93  		SiafundCount           types.Currency    `json:"siafundcount"`
    94  		SiafundPortion         *big.Rat          `json:"siafundportion"`
    95  		TargetWindow           types.BlockHeight `json:"targetwindow"`
    96  
    97  		InitialCoinbase uint64 `json:"initialcoinbase"`
    98  		MinimumCoinbase uint64 `json:"minimumcoinbase"`
    99  
   100  		RootTarget types.Target `json:"roottarget"`
   101  		RootDepth  types.Target `json:"rootdepth"`
   102  
   103  		DefaultAllowance modules.Allowance `json:"defaultallowance"`
   104  
   105  		// DEPRECATED: same values as MaxTargetAdjustmentUp and
   106  		// MaxTargetAdjustmentDown.
   107  		MaxAdjustmentUp   *big.Rat `json:"maxadjustmentup"`
   108  		MaxAdjustmentDown *big.Rat `json:"maxadjustmentdown"`
   109  
   110  		MaxTargetAdjustmentUp   *big.Rat `json:"maxtargetadjustmentup"`
   111  		MaxTargetAdjustmentDown *big.Rat `json:"maxtargetadjustmentdown"`
   112  
   113  		SiacoinPrecision types.Currency `json:"siacoinprecision"`
   114  	}
   115  
   116  	// DaemonVersion holds the version information for siad
   117  	DaemonVersion struct {
   118  		Version     string `json:"version"`
   119  		GitRevision string `json:"gitrevision"`
   120  		BuildTime   string `json:"buildtime"`
   121  	}
   122  )
   123  
   124  // fetchLatestRelease returns metadata about the most recent GitLab release.
   125  func fetchLatestRelease() (gitlabRelease, error) {
   126  	resp, err := http.Get("https://gitlab.com/api/v4/projects/7508674/repository/tags?order_by=name")
   127  	if err != nil {
   128  		return gitlabRelease{}, err
   129  	}
   130  	defer resp.Body.Close()
   131  	var releases []gitlabRelease
   132  	if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
   133  		return gitlabRelease{}, err
   134  	} else if len(releases) == 0 {
   135  		return gitlabRelease{}, errors.New("no releases found")
   136  	}
   137  	return releases[0], nil
   138  }
   139  
   140  // updateToRelease updates siad and siac to the release specified. siac is
   141  // assumed to be in the same folder as siad.
   142  func updateToRelease(version string) error {
   143  	updateOpts := update.Options{
   144  		Verifier: update.NewRSAVerifier(),
   145  	}
   146  	err := updateOpts.SetPublicKeyPEM([]byte(developerKey))
   147  	if err != nil {
   148  		// should never happen
   149  		return err
   150  	}
   151  
   152  	binaryFolder, err := osext.ExecutableFolder()
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	// download release archive
   158  	resp, err := http.Get(fmt.Sprintf("https://sia.tech/releases/Sia-%s-%s-%s.zip", version, runtime.GOOS, runtime.GOARCH))
   159  	if err != nil {
   160  		return err
   161  	}
   162  	// release should be small enough to store in memory (<10 MiB); use
   163  	// LimitReader to ensure we don't download more than 32 MiB
   164  	content, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<25))
   165  	resp.Body.Close()
   166  	if err != nil {
   167  		return err
   168  	}
   169  	r := bytes.NewReader(content)
   170  	z, err := zip.NewReader(r, r.Size())
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	// process zip, finding siad/siac binaries and signatures
   176  	for _, binary := range []string{"siad", "siac"} {
   177  		var binData io.ReadCloser
   178  		var signature []byte
   179  		var binaryName string // needed for TargetPath below
   180  		for _, zf := range z.File {
   181  			switch base := path.Base(zf.Name); base {
   182  			case binary, binary + ".exe":
   183  				binaryName = base
   184  				binData, err = zf.Open()
   185  				if err != nil {
   186  					return err
   187  				}
   188  				defer binData.Close()
   189  			case binary + ".sig", binary + ".exe.sig":
   190  				sigFile, err := zf.Open()
   191  				if err != nil {
   192  					return err
   193  				}
   194  				defer sigFile.Close()
   195  				signature, err = ioutil.ReadAll(sigFile)
   196  				if err != nil {
   197  					return err
   198  				}
   199  			}
   200  		}
   201  		if binData == nil {
   202  			return errors.New("could not find " + binary + " binary")
   203  		} else if signature == nil {
   204  			return errors.New("could not find " + binary + " signature")
   205  		}
   206  
   207  		// apply update
   208  		updateOpts.Signature = signature
   209  		updateOpts.TargetMode = 0775 // executable
   210  		updateOpts.TargetPath = filepath.Join(binaryFolder, binaryName)
   211  		err = update.Apply(binData, updateOpts)
   212  		if err != nil {
   213  			return err
   214  		}
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // daemonUpdateHandlerGET handles the API call that checks for an update.
   221  func (api *API) daemonUpdateHandlerGET(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   222  	release, err := fetchLatestRelease()
   223  	if err != nil {
   224  		WriteError(w, Error{Message: "Failed to fetch latest release: " + err.Error()}, http.StatusInternalServerError)
   225  		return
   226  	}
   227  	latestVersion := release.TagName[1:] // delete leading 'v'
   228  	WriteJSON(w, UpdateInfo{
   229  		Available: build.VersionCmp(latestVersion, build.Version) > 0,
   230  		Version:   latestVersion,
   231  	})
   232  }
   233  
   234  // daemonUpdateHandlerPOST handles the API call that updates siad and siac.
   235  // There is no safeguard to prevent "updating" to the same release, so callers
   236  // should always check the latest version via daemonUpdateHandlerGET first.
   237  // TODO: add support for specifying version to update to.
   238  func (api *API) daemonUpdateHandlerPOST(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   239  	release, err := fetchLatestRelease()
   240  	if err != nil {
   241  		WriteError(w, Error{Message: "Failed to fetch latest release: " + err.Error()}, http.StatusInternalServerError)
   242  		return
   243  	}
   244  	err = updateToRelease(release.TagName)
   245  	if err != nil {
   246  		if rerr := update.RollbackError(err); rerr != nil {
   247  			WriteError(w, Error{Message: "Serious error: Failed to rollback from bad update: " + rerr.Error()}, http.StatusInternalServerError)
   248  		} else {
   249  			WriteError(w, Error{Message: "Failed to apply update: " + err.Error()}, http.StatusInternalServerError)
   250  		}
   251  		return
   252  	}
   253  	WriteSuccess(w)
   254  }
   255  
   256  // debugConstantsHandler prints a json file containing all of the constants.
   257  func (api *API) daemonConstantsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   258  	sc := SiaConstants{
   259  		BlockFrequency:         types.BlockFrequency,
   260  		BlockSizeLimit:         types.BlockSizeLimit,
   261  		ExtremeFutureThreshold: types.ExtremeFutureThreshold,
   262  		FutureThreshold:        types.FutureThreshold,
   263  		GenesisTimestamp:       types.GenesisTimestamp,
   264  		MaturityDelay:          types.MaturityDelay,
   265  		MedianTimestampWindow:  types.MedianTimestampWindow,
   266  		SiafundCount:           types.SiafundCount,
   267  		SiafundPortion:         types.SiafundPortion,
   268  		TargetWindow:           types.TargetWindow,
   269  
   270  		InitialCoinbase: types.InitialCoinbase,
   271  		MinimumCoinbase: types.MinimumCoinbase,
   272  
   273  		RootTarget: types.RootTarget,
   274  		RootDepth:  types.RootDepth,
   275  
   276  		DefaultAllowance: modules.DefaultAllowance,
   277  
   278  		// DEPRECATED: same values as MaxTargetAdjustmentUp and
   279  		// MaxTargetAdjustmentDown.
   280  		MaxAdjustmentUp:   types.MaxTargetAdjustmentUp,
   281  		MaxAdjustmentDown: types.MaxTargetAdjustmentDown,
   282  
   283  		MaxTargetAdjustmentUp:   types.MaxTargetAdjustmentUp,
   284  		MaxTargetAdjustmentDown: types.MaxTargetAdjustmentDown,
   285  
   286  		SiacoinPrecision: types.SiacoinPrecision,
   287  	}
   288  
   289  	WriteJSON(w, sc)
   290  }
   291  
   292  // daemonVersionHandler handles the API call that requests the daemon's version.
   293  func (api *API) daemonVersionHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   294  	version := build.Version
   295  	if build.ReleaseTag != "" {
   296  		version += "-" + build.ReleaseTag
   297  	}
   298  	WriteJSON(w, DaemonVersion{Version: version, GitRevision: build.GitRevision, BuildTime: build.BuildTime})
   299  }
   300  
   301  // daemonStopHandler handles the API call to stop the daemon cleanly.
   302  func (api *API) daemonStopHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   303  	// can't write after we stop the server, so lie a bit.
   304  	WriteSuccess(w)
   305  
   306  	// need to flush the response before shutting down the server
   307  	f, ok := w.(http.Flusher)
   308  	if !ok {
   309  		panic("Server does not support flushing")
   310  	}
   311  	f.Flush()
   312  
   313  	if err := api.Shutdown(); err != nil {
   314  		build.Critical(err)
   315  	}
   316  }
   317  
   318  // DaemonSettingsGet contains information about global daemon settings.
   319  type DaemonSettingsGet struct {
   320  	MaxDownloadSpeed int64 `json:"maxdownloadspeed"`
   321  	MaxUploadSpeed   int64 `json:"maxuploadspeed"`
   322  }
   323  
   324  // daemonSettingsHandlerGET handles the API call asking for the daemon's
   325  // settings.
   326  func (api *API) daemonSettingsHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   327  	gmds, gmus, _ := modules.GlobalRateLimits.Limits()
   328  	WriteJSON(w, DaemonSettingsGet{gmds, gmus})
   329  }
   330  
   331  // daemonSettingsHandlerPOST handles the API call changing daemon specific
   332  // settings.
   333  func (api *API) daemonSettingsHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   334  	maxDownloadSpeed, maxUploadSpeed, _ := modules.GlobalRateLimits.Limits()
   335  	// Scan the download speed limit. (optional parameter)
   336  	if d := req.FormValue("maxdownloadspeed"); d != "" {
   337  		var downloadSpeed int64
   338  		if _, err := fmt.Sscan(d, &downloadSpeed); err != nil {
   339  			WriteError(w, Error{"unable to parse downloadspeed: " + err.Error()}, http.StatusBadRequest)
   340  			return
   341  		}
   342  		maxDownloadSpeed = downloadSpeed
   343  	}
   344  	// Scan the upload speed limit. (optional parameter)
   345  	if u := req.FormValue("maxuploadspeed"); u != "" {
   346  		var uploadSpeed int64
   347  		if _, err := fmt.Sscan(u, &uploadSpeed); err != nil {
   348  			WriteError(w, Error{"unable to parse uploadspeed: " + err.Error()}, http.StatusBadRequest)
   349  			return
   350  		}
   351  		maxUploadSpeed = uploadSpeed
   352  	}
   353  	// Set the limit.
   354  	if err := api.spdConfig.SetRatelimit(maxDownloadSpeed, maxUploadSpeed); err != nil {
   355  		WriteError(w, Error{"unable to set limits: " + err.Error()}, http.StatusBadRequest)
   356  		return
   357  	}
   358  	WriteSuccess(w)
   359  }