Enable pool closure (#80)

* Enable pool closure.

* Move homepage to its own file

* Docs and rename status>info
This commit is contained in:
Jamie Holdstock 2020-05-28 07:07:33 +01:00 committed by GitHub
parent e65a7fdbf3
commit 6a100811f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 96 additions and 75 deletions

View File

@ -29,6 +29,7 @@ var (
defaultWalletHost = "127.0.0.1" defaultWalletHost = "127.0.0.1"
defaultWebServerDebug = false defaultWebServerDebug = false
defaultBackupInterval = time.Minute * 3 defaultBackupInterval = time.Minute * 3
defaultVspClosed = false
) )
// config defines the configuration options for the VSP. // config defines the configuration options for the VSP.
@ -51,6 +52,7 @@ type config struct {
WebServerDebug bool `long:"webserverdebug" ini-name:"webserverdebug" description:"Enable web server debug mode (verbose logging to terminal and live-reloading templates)."` WebServerDebug bool `long:"webserverdebug" ini-name:"webserverdebug" description:"Enable web server debug mode (verbose logging to terminal and live-reloading templates)."`
SupportEmail string `long:"supportemail" ini-name:"supportemail" description:"Email address for users in need of support."` SupportEmail string `long:"supportemail" ini-name:"supportemail" description:"Email address for users in need of support."`
BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."` BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."`
VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."`
dbPath string dbPath string
netParams *netParams netParams *netParams
@ -156,6 +158,7 @@ func loadConfig() (*config, error) {
WalletHost: defaultWalletHost, WalletHost: defaultWalletHost,
WebServerDebug: defaultWebServerDebug, WebServerDebug: defaultWebServerDebug,
BackupInterval: defaultBackupInterval, BackupInterval: defaultBackupInterval,
VspClosed: defaultVspClosed,
} }
// Pre-parse the command line options to see if an alternative config // Pre-parse the command line options to see if an alternative config

View File

@ -15,13 +15,15 @@
## Expected usage ## Expected usage
### Get VSP public key ### Get VSP info
Clients should first retrieve the VSP's public key so they can check the Clients should retrieve the VSP's public key so they can check the signature on
signature on later API responses. A VSP should never change their public key so future API responses. A VSP should never change their public key so it can be
it can be requested once and cached indefinitely. requested once and cached indefinitely. `vspclosed` indicates that the VSP is
not currently accepting new tickets. Calling `/feeaddress` when a VSP is closed
will result in an error.
- `GET /api/pubkey` - `GET /api/vspinfo`
No request body. No request body.
@ -29,8 +31,11 @@ it can be requested once and cached indefinitely.
```json ```json
{ {
"timestamp":1590509065, "timestamp":1590599436,
"pubkey":"bLNwVVcda3LqRLv+m0J5sjd60+twjO/fuhcx8RUErDQ=" "pubkey":"SjAmrAqH7LScCUwM1qo5O6Cu7aKhrM1ORszgZwD7HmU=",
"feepercentage":0.05,
"vspclosed":false,
"network":"testnet3"
} }
``` ```
@ -101,11 +106,7 @@ has 6 confirmations.
} }
``` ```
### Information requests ### Ticket Status
Clients can check the status of the server at any time.
// TODO
Clients can check the status of a ticket at any time after calling Clients can check the status of a ticket at any time after calling
`/feeaddress`. `/feeaddress`.

View File

@ -44,6 +44,11 @@ func run(ctx context.Context) error {
return err return err
} }
if cfg.VspClosed {
log.Warnf("Config --vspclosed is set. This will prevent vspd from " +
"accepting new tickets.")
}
// Waitgroup for services to signal when they have shutdown cleanly. // Waitgroup for services to signal when they have shutdown cleanly.
var shutdownWg sync.WaitGroup var shutdownWg sync.WaitGroup
defer log.Info("Shutdown complete") defer log.Info("Shutdown complete")
@ -117,6 +122,7 @@ func run(ctx context.Context) error {
NetParams: cfg.netParams.Params, NetParams: cfg.netParams.Params,
FeeAddressExpiration: defaultFeeAddressExpiration, FeeAddressExpiration: defaultFeeAddressExpiration,
SupportEmail: cfg.SupportEmail, SupportEmail: cfg.SupportEmail,
VspClosed: cfg.VspClosed,
} }
err = webapi.Start(ctx, shutdownRequestChannel, &shutdownWg, cfg.Listen, db, err = webapi.Start(ctx, shutdownRequestChannel, &shutdownWg, cfg.Listen, db,
dcrdConnect, walletConnect, cfg.WebServerDebug, cfg.FeeXPub, apiCfg) dcrdConnect, walletConnect, cfg.WebServerDebug, cfg.FeeXPub, apiCfg)

View File

@ -69,6 +69,11 @@ func feeAddress(c *gin.Context) {
commitmentAddress := c.MustGet("CommitmentAddress").(string) commitmentAddress := c.MustGet("CommitmentAddress").(string)
dcrdClient := c.MustGet("DcrdClient").(*rpc.DcrdRPC) dcrdClient := c.MustGet("DcrdClient").(*rpc.DcrdRPC)
if cfg.VspClosed {
sendErrorResponse("pool is not accepting new tickets", http.StatusBadRequest, c)
return
}
var feeAddressRequest FeeAddressRequest var feeAddressRequest FeeAddressRequest
if err := binding.JSON.BindBody(rawRequest, &feeAddressRequest); err != nil { if err := binding.JSON.BindBody(rawRequest, &feeAddressRequest); err != nil {
log.Warnf("Bad feeaddress request from %s: %v", c.ClientIP(), err) log.Warnf("Bad feeaddress request from %s: %v", c.ClientIP(), err)

44
webapi/homepage.go Normal file
View File

@ -0,0 +1,44 @@
package webapi
import (
"net/http"
"time"
"github.com/jholdstock/vspd/database"
"github.com/gin-gonic/gin"
)
type vspStats struct {
PubKey []byte
TotalTickets int
FeePaidTickets int
VSPFee float64
Network string
UpdateTime string
SupportEmail string
VspClosed bool
}
var stats *vspStats
func updateVSPStats(db *database.VspDatabase, cfg Config) (*vspStats, error) {
total, feePaid, err := db.CountTickets()
if err != nil {
return nil, err
}
return &vspStats{
PubKey: signPubKey,
TotalTickets: total,
FeePaidTickets: feePaid,
VSPFee: cfg.VSPFee,
Network: cfg.NetParams.Name,
UpdateTime: time.Now().Format("Mon Jan _2 15:04:05 2006"),
SupportEmail: cfg.SupportEmail,
VspClosed: cfg.VspClosed,
}, nil
}
func homepage(c *gin.Context) {
c.HTML(http.StatusOK, "homepage.html", stats)
}

View File

@ -1,23 +0,0 @@
package webapi
import (
"time"
"github.com/gin-gonic/gin"
)
// pubKey is the handler for "GET /pubkey".
func pubKey(c *gin.Context) {
sendJSONResponse(pubKeyResponse{
Timestamp: time.Now().Unix(),
PubKey: signPubKey,
}, c)
}
// fee is the handler for "GET /fee".
func fee(c *gin.Context) {
sendJSONResponse(feeResponse{
Timestamp: time.Now().Unix(),
FeePercentage: cfg.VSPFee,
}, c)
}

View File

@ -48,6 +48,7 @@
<tr><td>Network:</td><td>{{ .Network }}</td></tr> <tr><td>Network:</td><td>{{ .Network }}</td></tr>
<tr><td>Support:</td><td>{{ .SupportEmail }}</td></tr> <tr><td>Support:</td><td>{{ .SupportEmail }}</td></tr>
<tr><td>Pubkey:</td><td>{{ printf "%x" .PubKey }}</td></tr> <tr><td>Pubkey:</td><td>{{ printf "%x" .PubKey }}</td></tr>
<tr><td>VSP closed:</td><td>{{ .VspClosed }}</td></tr>
</table> </table>
<p>Last updated: {{.UpdateTime}}</p> <p>Last updated: {{.UpdateTime}}</p>
</body> </body>

View File

@ -1,13 +1,11 @@
package webapi package webapi
type pubKeyResponse struct { type vspInfoResponse struct {
Timestamp int64 `json:"timestamp" binding:"required"` Timestamp int64 `json:"timestamp" binding:"required"`
PubKey []byte `json:"pubkey" binding:"required"` PubKey []byte `json:"pubkey" binding:"required"`
}
type feeResponse struct {
Timestamp int64 `json:"timestamp" binding:"required"`
FeePercentage float64 `json:"feepercentage" binding:"required"` FeePercentage float64 `json:"feepercentage" binding:"required"`
VspClosed bool `json:"vspclosed" binding:"required"`
Network string `json:"network" binding:"required"`
} }
type FeeAddressRequest struct { type FeeAddressRequest struct {

18
webapi/vspstatus.go Normal file
View File

@ -0,0 +1,18 @@
package webapi
import (
"time"
"github.com/gin-gonic/gin"
)
// vspInfo is the handler for "GET /vspinfo".
func vspInfo(c *gin.Context) {
sendJSONResponse(vspInfoResponse{
Timestamp: time.Now().Unix(),
PubKey: signPubKey,
FeePercentage: cfg.VSPFee,
Network: cfg.NetParams.Name,
VspClosed: cfg.VspClosed,
}, c)
}

View File

@ -24,6 +24,7 @@ type Config struct {
FeeAccountName string FeeAccountName string
FeeAddressExpiration time.Duration FeeAddressExpiration time.Duration
SupportEmail string SupportEmail string
VspClosed bool
} }
const ( const (
@ -34,18 +35,6 @@ const (
relayFee = 0.0001 relayFee = 0.0001
) )
type vspStats struct {
PubKey []byte
TotalTickets int
FeePaidTickets int
VSPFee float64
Network string
UpdateTime string
SupportEmail string
}
var stats *vspStats
var cfg Config var cfg Config
var db *database.VspDatabase var db *database.VspDatabase
var dcrdConnect rpc.Connect var dcrdConnect rpc.Connect
@ -185,8 +174,7 @@ func router(debugMode bool) *gin.Engine {
// These routes have no extra middleware. They can be accessed by anybody. // These routes have no extra middleware. They can be accessed by anybody.
router.GET("/", homepage) router.GET("/", homepage)
router.GET("/api/fee", fee) router.GET("/api/vspinfo", vspInfo)
router.GET("/api/pubkey", pubKey)
// These API routes access dcrd and they need authentication. // These API routes access dcrd and they need authentication.
feeOnly := router.Group("/api").Use( feeOnly := router.Group("/api").Use(
@ -206,26 +194,6 @@ func router(debugMode bool) *gin.Engine {
return router return router
} }
func updateVSPStats(db *database.VspDatabase, cfg Config) (*vspStats, error) {
total, feePaid, err := db.CountTickets()
if err != nil {
return nil, err
}
return &vspStats{
PubKey: signPubKey,
TotalTickets: total,
FeePaidTickets: feePaid,
VSPFee: cfg.VSPFee,
Network: cfg.NetParams.Name,
UpdateTime: time.Now().Format("Mon Jan _2 15:04:05 2006"),
SupportEmail: cfg.SupportEmail,
}, nil
}
func homepage(c *gin.Context) {
c.HTML(http.StatusOK, "homepage.html", stats)
}
func sendJSONResponse(resp interface{}, c *gin.Context) { func sendJSONResponse(resp interface{}, c *gin.Context) {
dec, err := json.Marshal(resp) dec, err := json.Marshal(resp)
if err != nil { if err != nil {