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 }