webapi: Rename server to WebAPI.

Exporting this struct is a step towards breaking up the Start func into
a New func and Run func, enabling calling code to use:

    api := webapi.New()
    api.Run()

WebAPI is a more suitable name than server because it matches the
package name, and also it helps to distinguish WebAPI from the HTTP
server it uses internally (ie. webapi.server rather than server.server).
This commit is contained in:
jholdstock 2023-09-15 09:53:49 +01:00 committed by Jamie Holdstock
parent 8a8cbe47b9
commit a7bb0cd9d7
11 changed files with 330 additions and 330 deletions

View File

@ -48,14 +48,14 @@ type searchResult struct {
MaxVoteChanges int
}
func (s *server) dcrdStatus(c *gin.Context) dcrdStatus {
func (w *WebAPI) dcrdStatus(c *gin.Context) dcrdStatus {
hostname := c.MustGet(dcrdHostKey).(string)
status := dcrdStatus{Host: hostname}
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("Could not get dcrd client: %v", dcrdErr.(error))
w.log.Errorf("Could not get dcrd client: %v", dcrdErr.(error))
return status
}
@ -63,7 +63,7 @@ func (s *server) dcrdStatus(c *gin.Context) dcrdStatus {
bestBlock, err := dcrdClient.GetBlockCount()
if err != nil {
s.log.Errorf("Could not get dcrd block count: %v", err)
w.log.Errorf("Could not get dcrd block count: %v", err)
status.BestBlockError = true
return status
}
@ -73,7 +73,7 @@ func (s *server) dcrdStatus(c *gin.Context) dcrdStatus {
return status
}
func (s *server) walletStatus(c *gin.Context) map[string]walletStatus {
func (w *WebAPI) walletStatus(c *gin.Context) map[string]walletStatus {
walletClients := c.MustGet(walletsKey).([]*rpc.WalletRPC)
failedWalletClients := c.MustGet(failedWalletsKey).([]string)
@ -83,7 +83,7 @@ func (s *server) walletStatus(c *gin.Context) map[string]walletStatus {
walletInfo, err := v.WalletInfo()
if err != nil {
s.log.Errorf("dcrwallet.WalletInfo error (wallet=%s): %v", v.String(), err)
w.log.Errorf("dcrwallet.WalletInfo error (wallet=%s): %v", v.String(), err)
ws.InfoError = true
} else {
ws.DaemonConnected = walletInfo.DaemonConnected
@ -94,7 +94,7 @@ func (s *server) walletStatus(c *gin.Context) map[string]walletStatus {
height, err := v.GetBestBlockHeight()
if err != nil {
s.log.Errorf("dcrwallet.GetBestBlockHeight error (wallet=%s): %v", v.String(), err)
w.log.Errorf("dcrwallet.GetBestBlockHeight error (wallet=%s): %v", v.String(), err)
ws.BestBlockError = true
} else {
ws.BestBlockHeight = height
@ -111,10 +111,10 @@ func (s *server) walletStatus(c *gin.Context) map[string]walletStatus {
// statusJSON is the handler for "GET /admin/status". It returns a JSON object
// describing the current status of voting wallets.
func (s *server) statusJSON(c *gin.Context) {
func (w *WebAPI) statusJSON(c *gin.Context) {
httpStatus := http.StatusOK
wallets := s.walletStatus(c)
wallets := w.walletStatus(c)
// Respond with HTTP status 500 if any voting wallets have issues.
for _, wallet := range wallets {
@ -129,7 +129,7 @@ func (s *server) statusJSON(c *gin.Context) {
}
}
dcrd := s.dcrdStatus(c)
dcrd := w.dcrdStatus(c)
// Respond with HTTP status 500 if dcrd has issues.
if !dcrd.Connected || dcrd.BestBlockError {
@ -143,37 +143,37 @@ func (s *server) statusJSON(c *gin.Context) {
}
// adminPage is the handler for "GET /admin".
func (s *server) adminPage(c *gin.Context) {
func (w *WebAPI) adminPage(c *gin.Context) {
c.HTML(http.StatusOK, "admin.html", gin.H{
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WalletStatus": s.walletStatus(c),
"DcrdStatus": s.dcrdStatus(c),
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
"WalletStatus": w.walletStatus(c),
"DcrdStatus": w.dcrdStatus(c),
})
}
// ticketSearch is the handler for "POST /admin/ticket". The hash param will be
// used to retrieve a ticket from the database.
func (s *server) ticketSearch(c *gin.Context) {
func (w *WebAPI) ticketSearch(c *gin.Context) {
hash := c.PostForm("hash")
ticket, found, err := s.db.GetTicketByHash(hash)
ticket, found, err := w.db.GetTicketByHash(hash)
if err != nil {
s.log.Errorf("db.GetTicketByHash error (ticketHash=%s): %v", hash, err)
w.log.Errorf("db.GetTicketByHash error (ticketHash=%s): %v", hash, err)
c.String(http.StatusInternalServerError, "Error getting ticket from db")
return
}
voteChanges, err := s.db.GetVoteChanges(hash)
voteChanges, err := w.db.GetVoteChanges(hash)
if err != nil {
s.log.Errorf("db.GetVoteChanges error (ticketHash=%s): %v", hash, err)
w.log.Errorf("db.GetVoteChanges error (ticketHash=%s): %v", hash, err)
c.String(http.StatusInternalServerError, "Error getting vote changes from db")
return
}
altSignAddrData, err := s.db.AltSignAddrData(hash)
altSignAddrData, err := w.db.AltSignAddrData(hash)
if err != nil {
s.log.Errorf("db.AltSignAddrData error (ticketHash=%s): %v", hash, err)
w.log.Errorf("db.AltSignAddrData error (ticketHash=%s): %v", hash, err)
c.String(http.StatusInternalServerError, "Error getting alt sig from db")
return
}
@ -186,21 +186,21 @@ func (s *server) ticketSearch(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("Could not get dcrd client: %v", dcrdErr.(error))
w.log.Errorf("Could not get dcrd client: %v", dcrdErr.(error))
c.String(http.StatusInternalServerError, "Could not get dcrd client")
return
}
resp, err := dcrdClient.DecodeRawTransaction(ticket.FeeTxHex)
if err != nil {
s.log.Errorf("dcrd.DecodeRawTransaction error: %w", err)
w.log.Errorf("dcrd.DecodeRawTransaction error: %w", err)
c.String(http.StatusInternalServerError, "Error decoding fee transaction")
return
}
decoded, err := json.Marshal(resp)
if err != nil {
s.log.Errorf("Unmarshal fee tx error: %w", err)
w.log.Errorf("Unmarshal fee tx error: %w", err)
c.String(http.StatusInternalServerError, "Error unmarshalling fee tx")
return
}
@ -216,45 +216,45 @@ func (s *server) ticketSearch(c *gin.Context) {
FeeTxDecoded: feeTxDecoded,
AltSignAddrData: altSignAddrData,
VoteChanges: voteChanges,
MaxVoteChanges: s.cfg.MaxVoteChangeRecords,
MaxVoteChanges: w.cfg.MaxVoteChangeRecords,
},
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WalletStatus": s.walletStatus(c),
"DcrdStatus": s.dcrdStatus(c),
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
"WalletStatus": w.walletStatus(c),
"DcrdStatus": w.dcrdStatus(c),
})
}
// adminLogin is the handler for "POST /admin". If a valid password is provided,
// the current session will be authenticated as an admin.
func (s *server) adminLogin(c *gin.Context) {
func (w *WebAPI) adminLogin(c *gin.Context) {
password := c.PostForm("password")
if password != s.cfg.AdminPass {
s.log.Warnf("Failed login attempt from %s", c.ClientIP())
if password != w.cfg.AdminPass {
w.log.Warnf("Failed login attempt from %s", c.ClientIP())
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
"FailedLoginMsg": "Incorrect password",
})
return
}
s.setAdminStatus(true, c)
w.setAdminStatus(true, c)
}
// adminLogout is the handler for "POST /admin/logout". The current session will
// have its admin authentication removed.
func (s *server) adminLogout(c *gin.Context) {
s.setAdminStatus(nil, c)
func (w *WebAPI) adminLogout(c *gin.Context) {
w.setAdminStatus(nil, c)
}
// downloadDatabaseBackup is the handler for "GET /backup". A binary
// representation of the whole database is generated and returned to the client.
func (s *server) downloadDatabaseBackup(c *gin.Context) {
err := s.db.BackupDB(c.Writer)
func (w *WebAPI) downloadDatabaseBackup(c *gin.Context) {
err := w.db.BackupDB(c.Writer)
if err != nil {
s.log.Errorf("Error backing up database: %v", err)
w.log.Errorf("Error backing up database: %v", err)
// Don't write any http body here because Content-Length has already
// been set in db.BackupDB. Status is enough to indicate an error.
c.Status(http.StatusInternalServerError)
@ -263,12 +263,12 @@ func (s *server) downloadDatabaseBackup(c *gin.Context) {
// setAdminStatus stores the authentication status of the current session and
// redirects the client to GET /admin.
func (s *server) setAdminStatus(admin any, c *gin.Context) {
func (w *WebAPI) setAdminStatus(admin any, c *gin.Context) {
session := c.MustGet(sessionKey).(*sessions.Session)
session.Values["admin"] = admin
err := session.Save(c.Request, c.Writer)
if err != nil {
s.log.Errorf("Error saving session: %v", err)
w.log.Errorf("Error saving session: %v", err)
c.String(http.StatusInternalServerError, "Error saving session")
return
}

View File

@ -24,16 +24,16 @@ var addrMtx sync.Mutex
// the last used address index in the database. In order to maintain consistency
// between the internal counter of address generator and the database, this func
// uses a mutex to ensure it is not run concurrently.
func (s *server) getNewFeeAddress() (string, uint32, error) {
func (w *WebAPI) getNewFeeAddress() (string, uint32, error) {
addrMtx.Lock()
defer addrMtx.Unlock()
addr, idx, err := s.addrGen.nextAddress()
addr, idx, err := w.addrGen.nextAddress()
if err != nil {
return "", 0, err
}
err = s.db.SetLastAddressIndex(idx)
err = w.db.SetLastAddressIndex(idx)
if err != nil {
return "", 0, err
}
@ -43,7 +43,7 @@ func (s *server) getNewFeeAddress() (string, uint32, error) {
// getCurrentFee returns the minimum fee amount a client should pay in order to
// register a ticket with the VSP at the current block height.
func (s *server) getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
func (w *WebAPI) getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
bestBlock, err := dcrdClient.GetBestBlockHeader()
if err != nil {
return 0, err
@ -56,10 +56,10 @@ func (s *server) getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error)
// is only used to calculate the fee charged for adding a ticket to the VSP.
const defaultMinRelayTxFee = dcrutil.Amount(1e4)
isDCP0010Active := s.cfg.Network.DCP10Active(int64(bestBlock.Height))
isDCP0010Active := w.cfg.Network.DCP10Active(int64(bestBlock.Height))
fee := txrules.StakePoolTicketFee(sDiff, defaultMinRelayTxFee,
int32(bestBlock.Height), s.cfg.VSPFee, s.cfg.Network.Params, isDCP0010Active)
int32(bestBlock.Height), w.cfg.VSPFee, w.cfg.Network.Params, isDCP0010Active)
if err != nil {
return 0, err
}
@ -67,7 +67,7 @@ func (s *server) getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error)
}
// feeAddress is the handler for "POST /api/v3/feeaddress".
func (s *server) feeAddress(c *gin.Context) {
func (w *WebAPI) feeAddress(c *gin.Context) {
const funcName = "feeAddress"
@ -78,16 +78,16 @@ func (s *server) feeAddress(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
w.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
var request types.FeeAddressRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -98,31 +98,31 @@ func (s *server) feeAddress(c *gin.Context) {
(ticket.FeeTxStatus == database.FeeReceieved ||
ticket.FeeTxStatus == database.FeeBroadcast ||
ticket.FeeTxStatus == database.FeeConfirmed) {
s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeAlreadyReceived, c)
w.sendError(types.ErrFeeAlreadyReceived, c)
return
}
// Get ticket details.
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
// Ensure this ticket is eligible to vote at some point in the future.
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.Network)
canVote, err := canTicketVote(rawTicket, dcrdClient, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticketHash)
s.sendError(types.ErrTicketCannotVote, c)
w.sendError(types.ErrTicketCannotVote, c)
return
}
@ -132,26 +132,26 @@ func (s *server) feeAddress(c *gin.Context) {
// If the expiry period has passed we need to issue a new fee.
now := time.Now()
if ticket.FeeExpired() {
newFee, err := s.getCurrentFee(dcrdClient)
newFee, err := w.getCurrentFee(dcrdClient)
if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err)
w.sendError(types.ErrInternalError, c)
return
}
ticket.FeeExpiration = now.Add(feeAddressExpiration).Unix()
ticket.FeeAmount = int64(newFee)
err = s.db.UpdateTicket(ticket)
err = w.db.UpdateTicket(ticket)
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
w.log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
s.log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)",
w.log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)",
funcName, newFee, ticket.Hash)
}
s.sendJSONResponse(types.FeeAddressResponse{
w.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(),
Request: reqBytes,
FeeAddress: ticket.FeeAddress,
@ -165,17 +165,17 @@ func (s *server) feeAddress(c *gin.Context) {
// Beyond this point we are processing a new ticket which the VSP has not
// seen before.
fee, err := s.getCurrentFee(dcrdClient)
fee, err := w.getCurrentFee(dcrdClient)
if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
newAddress, newAddressIdx, err := s.getNewFeeAddress()
newAddress, newAddressIdx, err := w.getNewFeeAddress()
if err != nil {
s.log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
@ -203,18 +203,18 @@ func (s *server) feeAddress(c *gin.Context) {
FeeTxStatus: database.NoFee,
}
err = s.db.InsertNewTicket(dbTicket)
err = w.db.InsertNewTicket(dbTicket)
if err != nil {
s.log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
s.log.Debugf("%s: Fee address created for new ticket: (tktConfirmed=%t, feeAddrIdx=%d, "+
w.log.Debugf("%s: Fee address created for new ticket: (tktConfirmed=%t, feeAddrIdx=%d, "+
"feeAddr=%s, feeAmt=%s, ticketHash=%s)",
funcName, confirmed, newAddressIdx, newAddress, fee, ticketHash)
s.sendJSONResponse(types.FeeAddressResponse{
w.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(),
Request: reqBytes,
FeeAddress: newAddress,

View File

@ -10,9 +10,9 @@ import (
"github.com/gin-gonic/gin"
)
func (s *server) homepage(c *gin.Context) {
func (w *WebAPI) homepage(c *gin.Context) {
c.HTML(http.StatusOK, "homepage.html", gin.H{
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
})
}

View File

@ -63,7 +63,7 @@ func rateLimit(limit rate.Limit, limitExceeded gin.HandlerFunc) gin.HandlerFunc
// withSession middleware adds a gorilla session to the request context for
// downstream handlers to make use of. Sessions are used by admin pages to
// maintain authentication status.
func (s *server) withSession(store *sessions.CookieStore) gin.HandlerFunc {
func (w *WebAPI) withSession(store *sessions.CookieStore) gin.HandlerFunc {
return func(c *gin.Context) {
session, err := store.Get(c.Request, "vspd-session")
if err != nil {
@ -71,18 +71,18 @@ func (s *server) withSession(store *sessions.CookieStore) gin.HandlerFunc {
// common during development (eg. when using the test harness) but
// it should not occur in production.
if strings.Contains(err.Error(), invalidCookieErr) {
s.log.Warn("Cookie secret has changed. Generating new session.")
w.log.Warn("Cookie secret has changed. Generating new session.")
// Persist the newly generated session.
err = store.Save(c.Request, c.Writer, session)
if err != nil {
s.log.Errorf("Error saving session: %v", err)
w.log.Errorf("Error saving session: %v", err)
c.String(http.StatusInternalServerError, "Error saving session")
c.Abort()
return
}
} else {
s.log.Errorf("Session error: %v", err)
w.log.Errorf("Session error: %v", err)
c.String(http.StatusInternalServerError, "Error getting session")
c.Abort()
return
@ -95,14 +95,14 @@ func (s *server) withSession(store *sessions.CookieStore) gin.HandlerFunc {
// requireAdmin will only allow the request to proceed if the current session is
// authenticated as an admin, otherwise it will render the login template.
func (s *server) requireAdmin(c *gin.Context) {
func (w *WebAPI) requireAdmin(c *gin.Context) {
session := c.MustGet(sessionKey).(*sessions.Session)
admin := session.Values["admin"]
if admin == nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
})
c.Abort()
return
@ -111,7 +111,7 @@ func (s *server) requireAdmin(c *gin.Context) {
// withDcrdClient middleware adds a dcrd client to the request context for
// downstream handlers to make use of.
func (s *server) withDcrdClient(dcrd rpc.DcrdConnect) gin.HandlerFunc {
func (w *WebAPI) withDcrdClient(dcrd rpc.DcrdConnect) gin.HandlerFunc {
return func(c *gin.Context) {
client, hostname, err := dcrd.Client()
// Don't handle the error here, add it to the context and let downstream
@ -125,13 +125,13 @@ func (s *server) withDcrdClient(dcrd rpc.DcrdConnect) gin.HandlerFunc {
// withWalletClients middleware attempts to add voting wallet clients to the
// request context for downstream handlers to make use of. Downstream handlers
// must handle the case where no wallet clients are connected.
func (s *server) withWalletClients(wallets rpc.WalletConnect) gin.HandlerFunc {
func (w *WebAPI) withWalletClients(wallets rpc.WalletConnect) gin.HandlerFunc {
return func(c *gin.Context) {
clients, failedConnections := wallets.Clients()
if len(clients) == 0 {
s.log.Error("Could not connect to any wallets")
w.log.Error("Could not connect to any wallets")
} else if len(failedConnections) > 0 {
s.log.Errorf("Failed to connect to %d wallet(s), proceeding with only %d",
w.log.Errorf("Failed to connect to %d wallet(s), proceeding with only %d",
len(failedConnections), len(clients))
}
c.Set(walletsKey, clients)
@ -151,9 +151,9 @@ func drainAndReplaceBody(req *http.Request) ([]byte, error) {
return reqBytes, nil
}
func (s *server) vspMustBeOpen(c *gin.Context) {
if s.cfg.VspClosed {
s.sendError(types.ErrVspClosed, c)
func (w *WebAPI) vspMustBeOpen(c *gin.Context) {
if w.cfg.VspClosed {
w.sendError(types.ErrVspClosed, c)
return
}
}
@ -163,14 +163,14 @@ func (s *server) vspMustBeOpen(c *gin.Context) {
// Ticket hash, ticket hex, and parent hex are parsed from the request body and
// validated. They are broadcast to the network using SendRawTransaction if dcrd
// is not aware of them.
func (s *server) broadcastTicket(c *gin.Context) {
func (w *WebAPI) broadcastTicket(c *gin.Context) {
const funcName = "broadcastTicket"
// Read request bytes.
reqBytes, err := drainAndReplaceBody(c.Request)
if err != nil {
s.log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -181,41 +181,41 @@ func (s *server) broadcastTicket(c *gin.Context) {
ParentHex string `json:"parenthex" binding:"required"`
}
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
// Ensure the provided ticket hex is a valid ticket.
msgTx, err := decodeTransaction(request.TicketHex)
if err != nil {
s.log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
w.log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendErrorWithMsg("cannot decode ticket hex", types.ErrBadRequest, c)
w.sendErrorWithMsg("cannot decode ticket hex", types.ErrBadRequest, c)
return
}
err = isValidTicket(msgTx)
if err != nil {
s.log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), request.TicketHash, err)
s.sendError(types.ErrInvalidTicket, c)
w.sendError(types.ErrInvalidTicket, c)
return
}
// Ensure hex matches hash.
if msgTx.TxHash().String() != request.TicketHash {
s.log.Warnf("%s: Ticket hex/hash mismatch (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Ticket hex/hash mismatch (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), request.TicketHash)
s.sendErrorWithMsg("ticket hex does not match hash", types.ErrBadRequest, c)
w.sendErrorWithMsg("ticket hex does not match hash", types.ErrBadRequest, c)
return
}
// Ensure the provided parent hex is a valid tx.
parentTx, err := decodeTransaction(request.ParentHex)
if err != nil {
s.log.Errorf("%s: Failed to decode parent hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
s.sendErrorWithMsg("cannot decode parent hex", types.ErrBadRequest, c)
w.log.Errorf("%s: Failed to decode parent hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
w.sendErrorWithMsg("cannot decode parent hex", types.ErrBadRequest, c)
return
}
parentHash := parentTx.TxHash()
@ -224,8 +224,8 @@ func (s *server) broadcastTicket(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
w.sendError(types.ErrInternalError, c)
return
}
@ -248,24 +248,24 @@ func (s *server) broadcastTicket(c *gin.Context) {
}
if !found {
s.log.Errorf("%s: Invalid ticket parent (ticketHash=%s)", funcName, request.TicketHash)
s.sendErrorWithMsg("invalid ticket parent", types.ErrBadRequest, c)
w.log.Errorf("%s: Invalid ticket parent (ticketHash=%s)", funcName, request.TicketHash)
w.sendErrorWithMsg("invalid ticket parent", types.ErrBadRequest, c)
return
}
s.log.Debugf("%s: Broadcasting parent tx %s (ticketHash=%s)", funcName, parentHash, request.TicketHash)
w.log.Debugf("%s: Broadcasting parent tx %s (ticketHash=%s)", funcName, parentHash, request.TicketHash)
err = dcrdClient.SendRawTransaction(request.ParentHex)
if err != nil {
s.log.Errorf("%s: dcrd.SendRawTransaction for parent tx failed (ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.SendRawTransaction for parent tx failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(types.ErrCannotBroadcastTicket, c)
w.sendError(types.ErrCannotBroadcastTicket, c)
return
}
} else {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket parent failed (ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket parent failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
}
@ -281,18 +281,18 @@ func (s *server) broadcastTicket(c *gin.Context) {
// hex, so we can broadcast it here.
var e *wsrpc.Error
if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo {
s.log.Debugf("%s: Broadcasting ticket (ticketHash=%s)", funcName, request.TicketHash)
w.log.Debugf("%s: Broadcasting ticket (ticketHash=%s)", funcName, request.TicketHash)
err = dcrdClient.SendRawTransaction(request.TicketHex)
if err != nil {
s.log.Errorf("%s: dcrd.SendRawTransaction for ticket failed (ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.SendRawTransaction for ticket failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(types.ErrCannotBroadcastTicket, c)
w.sendError(types.ErrCannotBroadcastTicket, c)
return
}
} else {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
}
@ -304,14 +304,14 @@ func (s *server) broadcastTicket(c *gin.Context) {
// does not contain the request body signed with the commitment address.
// Ticket information is added to the request context for downstream handlers to
// use.
func (s *server) vspAuth(c *gin.Context) {
func (w *WebAPI) vspAuth(c *gin.Context) {
const funcName = "vspAuth"
// Read request bytes.
reqBytes, err := drainAndReplaceBody(c.Request)
if err != nil {
s.log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -324,8 +324,8 @@ func (s *server) vspAuth(c *gin.Context) {
TicketHash string `json:"tickethash" binding:"required"`
}
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
hash := request.TicketHash
@ -333,16 +333,16 @@ func (s *server) vspAuth(c *gin.Context) {
// Before hitting the db or any RPC, ensure this is a valid ticket hash.
err = validateTicketHash(hash)
if err != nil {
s.log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg("invalid ticket hash", types.ErrBadRequest, c)
w.log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg("invalid ticket hash", types.ErrBadRequest, c)
return
}
// Check if this ticket already appears in the database.
ticket, ticketFound, err := s.db.GetTicketByHash(hash)
ticket, ticketFound, err := w.db.GetTicketByHash(hash)
if err != nil {
s.log.Errorf("%s: db.GetTicketByHash error (ticketHash=%s): %v", funcName, hash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: db.GetTicketByHash error (ticketHash=%s): %v", funcName, hash, err)
w.sendError(types.ErrInternalError, c)
return
}
@ -357,41 +357,41 @@ func (s *server) vspAuth(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client (clientIP=%s, ticketHash=%s): %v",
w.log.Errorf("%s: Could not get dcrd client (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, dcrdErr.(error))
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
rawTx, err := dcrdClient.GetRawTransaction(hash)
if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (clientIP=%s, ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
msgTx, err := decodeTransaction(rawTx.Hex)
if err != nil {
s.log.Errorf("%s: Failed to decode ticket hex (clientIP=%s, ticketHash=%s): %v",
w.log.Errorf("%s: Failed to decode ticket hex (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
err = isValidTicket(msgTx)
if err != nil {
s.log.Errorf("%s: Invalid ticket (clientIP=%s, ticketHash=%s)",
w.log.Errorf("%s: Invalid ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), hash)
s.sendError(types.ErrInvalidTicket, c)
w.sendError(types.ErrInvalidTicket, c)
return
}
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, s.cfg.Network)
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: AddrFromSStxPkScrCommitment error (clientIP=%s, ticketHash=%s): %v",
w.log.Errorf("%s: AddrFromSStxPkScrCommitment error (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
@ -401,17 +401,17 @@ func (s *server) vspAuth(c *gin.Context) {
// Ensure a signature is provided.
signature := c.GetHeader("VSP-Client-Signature")
if signature == "" {
s.log.Warnf("%s: No VSP-Client-Signature header (clientIP=%s)", funcName, c.ClientIP())
s.sendErrorWithMsg("no VSP-Client-Signature header", types.ErrBadRequest, c)
w.log.Warnf("%s: No VSP-Client-Signature header (clientIP=%s)", funcName, c.ClientIP())
w.sendErrorWithMsg("no VSP-Client-Signature header", types.ErrBadRequest, c)
return
}
// Validate request signature to ensure ticket ownership.
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes), s.db, s.cfg.Network)
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes), w.db, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: Couldn't validate signature (clientIP=%s, ticketHash=%s): %v",
w.log.Errorf("%s: Couldn't validate signature (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrBadSignature, c)
w.sendError(types.ErrBadSignature, c)
return
}

View File

@ -21,7 +21,7 @@ import (
)
// payFee is the handler for "POST /api/v3/payfee".
func (s *server) payFee(c *gin.Context) {
func (w *WebAPI) payFee(c *gin.Context) {
const funcName = "payFee"
// Get values which have been added to context by middleware.
@ -30,22 +30,22 @@ func (s *server) payFee(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
w.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c)
w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
w.sendError(types.ErrUnknownTicket, c)
return
}
var request types.PayFeeRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -53,49 +53,49 @@ func (s *server) payFee(c *gin.Context) {
if ticket.FeeTxStatus == database.FeeReceieved ||
ticket.FeeTxStatus == database.FeeBroadcast ||
ticket.FeeTxStatus == database.FeeConfirmed {
s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeAlreadyReceived, c)
w.sendError(types.ErrFeeAlreadyReceived, c)
return
}
// Get ticket details.
rawTicket, err := dcrdClient.GetRawTransaction(ticket.Hash)
if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
w.sendError(types.ErrInternalError, c)
return
}
// Ensure this ticket is eligible to vote at some point in the future.
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.Network)
canVote, err := canTicketVote(rawTicket, dcrdClient, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err)
w.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrTicketCannotVote, c)
w.sendError(types.ErrTicketCannotVote, c)
return
}
// Respond early if the fee for this ticket is expired.
if ticket.FeeExpired() {
s.log.Warnf("%s: Expired payfee request (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Expired payfee request (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeExpired, c)
w.sendError(types.ErrFeeExpired, c)
return
}
// Validate VotingKey.
votingKey := request.VotingKey
votingWIF, err := dcrutil.DecodeWIF(votingKey, s.cfg.Network.PrivateKeyID)
votingWIF, err := dcrutil.DecodeWIF(votingKey, w.cfg.Network.PrivateKeyID)
if err != nil {
s.log.Warnf("%s: Failed to decode WIF (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Failed to decode WIF (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidPrivKey, c)
w.sendError(types.ErrInvalidPrivKey, c)
return
}
@ -103,10 +103,10 @@ func (s *server) payFee(c *gin.Context) {
// the ticket should still be registered.
validVoteChoices := true
err = validConsensusVoteChoices(s.cfg.Network, s.cfg.Network.CurrentVoteVersion(), request.VoteChoices)
err = validConsensusVoteChoices(w.cfg.Network, w.cfg.Network.CurrentVoteVersion(), request.VoteChoices)
if err != nil {
validVoteChoices = false
s.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
}
@ -114,7 +114,7 @@ func (s *server) payFee(c *gin.Context) {
err = validTreasuryPolicy(request.TreasuryPolicy)
if err != nil {
validTreasury = false
s.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
}
@ -122,33 +122,33 @@ func (s *server) payFee(c *gin.Context) {
err = validTSpendPolicy(request.TSpendPolicy)
if err != nil {
validTSpend = false
s.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
}
// Validate FeeTx.
feeTx, err := decodeTransaction(request.FeeTx)
if err != nil {
s.log.Warnf("%s: Failed to decode fee tx hex (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Failed to decode fee tx hex (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidFeeTx, c)
w.sendError(types.ErrInvalidFeeTx, c)
return
}
err = blockchain.CheckTransactionSanity(feeTx, uint64(s.cfg.Network.MaxTxSize))
err = blockchain.CheckTransactionSanity(feeTx, uint64(w.cfg.Network.MaxTxSize))
if err != nil {
s.log.Warnf("%s: Fee tx failed sanity check (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Fee tx failed sanity check (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidFeeTx, c)
w.sendError(types.ErrInvalidFeeTx, c)
return
}
// Decode fee address to get its payment script details.
feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, s.cfg.Network)
feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: Failed to decode fee address (ticketHash=%s): %v",
w.log.Errorf("%s: Failed to decode fee address (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
@ -166,9 +166,9 @@ func (s *server) payFee(c *gin.Context) {
// Confirm a fee payment was found.
if feePaid == 0 {
s.log.Warnf("%s: Fee tx did not include expected payment (ticketHash=%s, feeAddress=%s, clientIP=%s)",
w.log.Warnf("%s: Fee tx did not include expected payment (ticketHash=%s, feeAddress=%s, clientIP=%s)",
funcName, ticket.Hash, ticket.FeeAddress, c.ClientIP())
s.sendErrorWithMsg(
w.sendErrorWithMsg(
fmt.Sprintf("feetx did not include any payments for fee address %s", ticket.FeeAddress),
types.ErrInvalidFeeTx, c)
return
@ -177,19 +177,19 @@ func (s *server) payFee(c *gin.Context) {
// Confirm fee payment is equal to or larger than the minimum expected.
minFee := dcrutil.Amount(ticket.FeeAmount)
if feePaid < minFee {
s.log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
w.log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
s.sendError(types.ErrFeeTooSmall, c)
w.sendError(types.ErrFeeTooSmall, c)
return
}
// Decode the provided voting WIF to get its voting rights script.
pkHash := stdaddr.Hash160(votingWIF.PubKey())
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, s.cfg.Network)
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: Failed to get voting address from WIF (ticketHash=%s, clientIP=%s): %v",
w.log.Errorf("%s: Failed to get voting address from WIF (ticketHash=%s, clientIP=%s): %v",
funcName, ticket.Hash, c.ClientIP(), err)
s.sendError(types.ErrInvalidPrivKey, c)
w.sendError(types.ErrInvalidPrivKey, c)
return
}
@ -198,9 +198,9 @@ func (s *server) payFee(c *gin.Context) {
// Decode ticket transaction to get its voting rights script.
ticketTx, err := decodeTransaction(rawTicket.Hex)
if err != nil {
s.log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
w.log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
@ -210,9 +210,9 @@ func (s *server) payFee(c *gin.Context) {
// Ensure provided voting WIF matches the actual voting address of the
// ticket. Both script and script version should match.
if actualScriptVer != wantScriptVer || !bytes.Equal(actualScript, wantScript) {
s.log.Warnf("%s: Voting address does not match provided private key: (ticketHash=%s)",
w.log.Warnf("%s: Voting address does not match provided private key: (ticketHash=%s)",
funcName, ticket.Hash)
s.sendErrorWithMsg("voting address does not match provided private key",
w.sendErrorWithMsg("voting address does not match provided private key",
types.ErrInvalidPrivKey, c)
return
}
@ -238,35 +238,35 @@ func (s *server) payFee(c *gin.Context) {
ticket.TreasuryPolicy = request.TreasuryPolicy
}
err = s.db.UpdateTicket(ticket)
err = w.db.UpdateTicket(ticket)
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx (ticketHash=%s): %v",
w.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
s.log.Debugf("%s: Fee tx received for ticket (minExpectedFee=%v, feePaid=%v, ticketHash=%s)",
w.log.Debugf("%s: Fee tx received for ticket (minExpectedFee=%v, feePaid=%v, ticketHash=%s)",
funcName, minFee, feePaid, ticket.Hash)
if ticket.Confirmed {
err = dcrdClient.SendRawTransaction(request.FeeTx)
if err != nil {
s.log.Errorf("%s: dcrd.SendRawTransaction for fee tx failed (ticketHash=%s): %v",
w.log.Errorf("%s: dcrd.SendRawTransaction for fee tx failed (ticketHash=%s): %v",
funcName, ticket.Hash, err)
ticket.FeeTxStatus = database.FeeError
// Send the client an explicit error if the issue is unknown outputs.
if strings.Contains(err.Error(), rpc.ErrUnknownOutputs) {
s.sendError(types.ErrCannotBroadcastFeeUnknownOutputs, c)
w.sendError(types.ErrCannotBroadcastFeeUnknownOutputs, c)
} else {
s.sendError(types.ErrCannotBroadcastFee, c)
w.sendError(types.ErrCannotBroadcastFee, c)
}
err = s.db.UpdateTicket(ticket)
err = w.db.UpdateTicket(ticket)
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx error (ticketHash=%s): %v",
w.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx error (ticketHash=%s): %v",
funcName, ticket.Hash, err)
}
@ -275,26 +275,26 @@ func (s *server) payFee(c *gin.Context) {
ticket.FeeTxStatus = database.FeeBroadcast
err = s.db.UpdateTicket(ticket)
err = w.db.UpdateTicket(ticket)
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
w.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
s.log.Debugf("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)",
w.log.Debugf("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)",
funcName, ticket.Hash, ticket.FeeTxHash)
}
// Send success response to client.
resp, respSig := s.sendJSONResponse(types.PayFeeResponse{
resp, respSig := w.sendJSONResponse(types.PayFeeResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)
// Store a record of the vote choice change.
err = s.db.SaveVoteChange(
err = w.db.SaveVoteChange(
ticket.Hash,
database.VoteChangeRecord{
Request: string(reqBytes),
@ -303,6 +303,6 @@ func (s *server) payFee(c *gin.Context) {
ResponseSignature: respSig,
})
if err != nil {
s.log.Errorf("%s: Failed to store vote change record (ticketHash=%s): %v", err)
w.log.Errorf("%s: Failed to store vote change record (ticketHash=%s): %v", err)
}
}

View File

@ -26,7 +26,7 @@ type node interface {
}
// setAltSignAddr is the handler for "POST /api/v3/setaltsignaddr".
func (s *server) setAltSignAddr(c *gin.Context) {
func (w *WebAPI) setAltSignAddr(c *gin.Context) {
const funcName = "setAltSignAddr"
@ -34,72 +34,72 @@ func (s *server) setAltSignAddr(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(node)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
w.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
var request types.SetAltSignAddrRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
altSignAddr, ticketHash := request.AltSignAddress, request.TicketHash
currentData, err := s.db.AltSignAddrData(ticketHash)
currentData, err := w.db.AltSignAddrData(ticketHash)
if err != nil {
s.log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
if currentData != nil {
msg := "alternate sign address data already exists"
s.log.Warnf("%s: %s (ticketHash=%s)", funcName, msg, ticketHash)
s.sendErrorWithMsg(msg, types.ErrBadRequest, c)
w.log.Warnf("%s: %s (ticketHash=%s)", funcName, msg, ticketHash)
w.sendErrorWithMsg(msg, types.ErrBadRequest, c)
return
}
// Fail fast if the pubkey doesn't decode properly.
addr, err := stdaddr.DecodeAddressV0(altSignAddr, s.cfg.Network)
addr, err := stdaddr.DecodeAddressV0(altSignAddr, w.cfg.Network)
if err != nil {
s.log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
if _, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok {
s.log.Warnf("%s: Alt sign address is unexpected type (clientIP=%s, type=%T)", funcName, c.ClientIP(), addr)
s.sendErrorWithMsg("wrong type for alternate signing address", types.ErrBadRequest, c)
w.log.Warnf("%s: Alt sign address is unexpected type (clientIP=%s, type=%T)", funcName, c.ClientIP(), addr)
w.sendErrorWithMsg("wrong type for alternate signing address", types.ErrBadRequest, c)
return
}
// Get ticket details.
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
// Ensure this ticket is eligible to vote at some point in the future.
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.Network)
canVote, err := canTicketVote(rawTicket, dcrdClient, w.cfg.Network)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
w.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: unvotable ticket (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticketHash)
s.sendError(types.ErrTicketCannotVote, c)
w.sendError(types.ErrTicketCannotVote, c)
return
}
// Send success response to client.
resp, respSig := s.sendJSONResponse(types.SetAltSignAddrResponse{
resp, respSig := w.sendJSONResponse(types.SetAltSignAddrResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)
@ -112,12 +112,12 @@ func (s *server) setAltSignAddr(c *gin.Context) {
RespSig: respSig,
}
err = s.db.InsertAltSignAddr(ticketHash, data)
err = w.db.InsertAltSignAddr(ticketHash, data)
if err != nil {
s.log.Errorf("%s: db.InsertAltSignAddr error (ticketHash=%s): %v",
w.log.Errorf("%s: db.InsertAltSignAddr error (ticketHash=%s): %v",
funcName, ticketHash, err)
return
}
s.log.Debugf("%s: New alt sign address set for ticket: (ticketHash=%s)", funcName, ticketHash)
w.log.Debugf("%s: New alt sign address set for ticket: (ticketHash=%s)", funcName, ticketHash)
}

View File

@ -39,7 +39,7 @@ var (
seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
feeXPub = "feexpub"
maxVoteChangeRecords = 3
api *server
api *WebAPI
)
// randBytes returns a byte slice of size n filled with random bytes.
@ -84,7 +84,7 @@ func TestMain(m *testing.M) {
panic(fmt.Errorf("error opening test database: %w", err))
}
api = &server{
api = &WebAPI{
cfg: cfg,
signPrivKey: signPrivKey,
db: db,

View File

@ -17,7 +17,7 @@ import (
)
// setVoteChoices is the handler for "POST /api/v3/setvotechoices".
func (s *server) setVoteChoices(c *gin.Context) {
func (w *WebAPI) setVoteChoices(c *gin.Context) {
const funcName = "setVoteChoices"
// Get values which have been added to context by middleware.
@ -29,46 +29,46 @@ func (s *server) setVoteChoices(c *gin.Context) {
// If we cannot set the vote choices on at least one voting wallet right
// now, don't update the database, just return an error.
if len(walletClients) == 0 {
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c)
w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
w.sendError(types.ErrUnknownTicket, c)
return
}
if ticket.FeeTxStatus == database.NoFee {
s.log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeNotReceived, c)
w.sendError(types.ErrFeeNotReceived, c)
return
}
// Only allow vote choices to be updated for mempool/immature/live tickets.
if ticket.Outcome != "" {
s.log.Warnf("%s: Ticket not eligible to vote (clientIP=%s, ticketHash=%s)",
w.log.Warnf("%s: Ticket not eligible to vote (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome),
w.sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome),
types.ErrTicketCannotVote, c)
return
}
var request types.SetVoteChoicesRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
// Return an error if this request has a timestamp older than any previous
// vote change requests. This is to prevent requests from being replayed.
previousChanges, err := s.db.GetVoteChanges(ticket.Hash)
previousChanges, err := w.db.GetVoteChanges(ticket.Hash)
if err != nil {
s.log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v",
w.log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
@ -78,43 +78,43 @@ func (s *server) setVoteChoices(c *gin.Context) {
}
err := json.Unmarshal([]byte(change.Request), &prevReq)
if err != nil {
s.log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v",
w.log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
if request.Timestamp <= prevReq.Timestamp {
s.log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+
w.log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+
"than %d (ticketHash=%s)",
funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash)
s.sendError(types.ErrInvalidTimestamp, c)
w.sendError(types.ErrInvalidTimestamp, c)
return
}
}
// Validate vote choices (consensus, tspend policy and treasury policy).
err = validConsensusVoteChoices(s.cfg.Network, s.cfg.Network.CurrentVoteVersion(), request.VoteChoices)
err = validConsensusVoteChoices(w.cfg.Network, w.cfg.Network.CurrentVoteVersion(), request.VoteChoices)
if err != nil {
s.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
w.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
return
}
err = validTreasuryPolicy(request.TreasuryPolicy)
if err != nil {
s.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
w.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
}
err = validTSpendPolicy(request.TSpendPolicy)
if err != nil {
s.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
w.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
w.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
}
// Update voting preferences in the database before updating the wallets. DB
@ -132,11 +132,11 @@ func (s *server) setVoteChoices(c *gin.Context) {
ticket.TreasuryPolicy[newTreasuryKey] = newChoice
}
err = s.db.UpdateTicket(ticket)
err = w.db.UpdateTicket(ticket)
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v",
w.log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.sendError(types.ErrInternalError, c)
return
}
@ -152,7 +152,7 @@ func (s *server) setVoteChoices(c *gin.Context) {
for agenda, choice := range ticket.VoteChoices {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil {
s.log.Errorf("%s: dcrwallet.SetVoteChoice failed (wallet=%s, ticketHash=%s): %v",
w.log.Errorf("%s: dcrwallet.SetVoteChoice failed (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), ticket.Hash, err)
}
}
@ -161,7 +161,7 @@ func (s *server) setVoteChoices(c *gin.Context) {
for tspend, policy := range ticket.TSpendPolicy {
err = walletClient.SetTSpendPolicy(tspend, policy, ticket.Hash)
if err != nil {
s.log.Errorf("%s: dcrwallet.SetTSpendPolicy failed (wallet=%s, ticketHash=%s): %v",
w.log.Errorf("%s: dcrwallet.SetTSpendPolicy failed (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), ticket.Hash, err)
}
}
@ -170,23 +170,23 @@ func (s *server) setVoteChoices(c *gin.Context) {
for key, policy := range ticket.TreasuryPolicy {
err = walletClient.SetTreasuryPolicy(key, policy, ticket.Hash)
if err != nil {
s.log.Errorf("%s: dcrwallet.SetTreasuryPolicy failed (wallet=%s, ticketHash=%s): %v",
w.log.Errorf("%s: dcrwallet.SetTreasuryPolicy failed (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), ticket.Hash, err)
}
}
}
}
s.log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash)
w.log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash)
// Send success response to client.
resp, respSig := s.sendJSONResponse(types.SetVoteChoicesResponse{
resp, respSig := w.sendJSONResponse(types.SetVoteChoicesResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)
// Store a record of the vote choice change.
err = s.db.SaveVoteChange(
err = w.db.SaveVoteChange(
ticket.Hash,
database.VoteChangeRecord{
Request: string(reqBytes),
@ -195,6 +195,6 @@ func (s *server) setVoteChoices(c *gin.Context) {
ResponseSignature: respSig,
})
if err != nil {
s.log.Errorf("%s: Failed to store vote change record (ticketHash=%s): %v", err)
w.log.Errorf("%s: Failed to store vote change record (ticketHash=%s): %v", err)
}
}

View File

@ -14,7 +14,7 @@ import (
)
// ticketStatus is the handler for "POST /api/v3/ticketstatus".
func (s *server) ticketStatus(c *gin.Context) {
func (w *WebAPI) ticketStatus(c *gin.Context) {
const funcName = "ticketStatus"
// Get values which have been added to context by middleware.
@ -23,23 +23,23 @@ func (s *server) ticketStatus(c *gin.Context) {
reqBytes := c.MustGet(requestBytesKey).([]byte)
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c)
w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
w.sendError(types.ErrUnknownTicket, c)
return
}
var request types.TicketStatusRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
// Get altSignAddress from database
altSignAddrData, err := s.db.AltSignAddrData(ticket.Hash)
altSignAddrData, err := w.db.AltSignAddrData(ticket.Hash)
if err != nil {
s.log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err)
w.sendError(types.ErrInternalError, c)
return
}
@ -48,7 +48,7 @@ func (s *server) ticketStatus(c *gin.Context) {
altSignAddr = altSignAddrData.AltSignAddr
}
s.sendJSONResponse(types.TicketStatusResponse{
w.sendJSONResponse(types.TicketStatusResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
TicketConfirmed: ticket.Confirmed,

View File

@ -13,16 +13,16 @@ import (
)
// vspInfo is the handler for "GET /api/v3/vspinfo".
func (s *server) vspInfo(c *gin.Context) {
cachedStats := s.cache.getData()
s.sendJSONResponse(types.VspInfoResponse{
func (w *WebAPI) vspInfo(c *gin.Context) {
cachedStats := w.cache.getData()
w.sendJSONResponse(types.VspInfoResponse{
APIVersions: []int64{3},
Timestamp: time.Now().Unix(),
PubKey: s.signPubKey,
FeePercentage: s.cfg.VSPFee,
Network: s.cfg.Network.Name,
VspClosed: s.cfg.VspClosed,
VspClosedMsg: s.cfg.VspClosedMsg,
PubKey: w.signPubKey,
FeePercentage: w.cfg.VSPFee,
Network: w.cfg.Network.Name,
VspClosed: w.cfg.VspClosed,
VspClosedMsg: w.cfg.VspClosedMsg,
VspdVersion: version.String(),
Voting: cachedStats.Voting,
Voted: cachedStats.Voted,

View File

@ -65,7 +65,7 @@ const (
commitmentAddressKey = "CommitmentAddress"
)
type server struct {
type WebAPI struct {
cfg Config
db *database.VspDatabase
log slog.Logger
@ -114,7 +114,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
return fmt.Errorf("db.GetCookieSecret error: %w", err)
}
s := &server{
w := &WebAPI{
cfg: cfg,
db: vdb,
log: log,
@ -133,7 +133,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
log.Infof("Listening on %s", cfg.Listen)
srv := http.Server{
Handler: s.router(cookieSecret, dcrd, wallets),
Handler: w.router(cookieSecret, dcrd, wallets),
ReadTimeout: 5 * time.Second, // slow requests should not hold connections opened
WriteTimeout: 60 * time.Second, // hung responses must die
}
@ -170,7 +170,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
// Periodically update cached VSP stats.
var refresh time.Duration
if s.cfg.Debug {
if w.cfg.Debug {
refresh = 1 * time.Second
} else {
refresh = 1 * time.Minute
@ -183,7 +183,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
shutdownWg.Done()
return
case <-time.After(refresh):
err := s.cache.update()
err := w.cache.update()
if err != nil {
log.Errorf("Failed to update cached VSP stats: %v", err)
}
@ -194,16 +194,16 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
return nil
}
func (s *server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.WalletConnect) *gin.Engine {
func (w *WebAPI) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.WalletConnect) *gin.Engine {
// With release mode enabled, gin will only read template files once and cache them.
// With release mode disabled, templates will be reloaded on the fly.
if !s.cfg.Debug {
if !w.cfg.Debug {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
explorerURL := s.cfg.Network.BlockExplorerURL
explorerURL := w.cfg.Network.BlockExplorerURL
// Add custom functions for use in templates.
router.SetFuncMap(template.FuncMap{
@ -212,7 +212,7 @@ func (s *server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
"blockURL": blockURL(explorerURL),
"dateTime": dateTime,
"stripWss": stripWss,
"indentJSON": indentJSON(s.log),
"indentJSON": indentJSON(w.log),
"atomsToDCR": atomsToDCR,
"float32ToPercent": float32ToPercent,
"comma": humanize.Comma,
@ -223,9 +223,9 @@ func (s *server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
// Recovery middleware handles any go panics generated while processing web
// requests. Ensures a 500 response is sent to the client rather than
// sending no response at all.
router.Use(recovery(s.log))
router.Use(recovery(w.log))
if s.cfg.Debug {
if w.cfg.Debug {
// Logger middleware outputs very detailed logging of webserver requests
// to the terminal. Does not get logged to file.
router.Use(gin.Logger())
@ -240,47 +240,47 @@ func (s *server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
// API routes.
api := router.Group("/api/v3")
api.GET("/vspinfo", s.vspInfo)
api.POST("/setaltsignaddr", s.vspMustBeOpen, s.withDcrdClient(dcrd), s.broadcastTicket, s.vspAuth, s.setAltSignAddr)
api.POST("/feeaddress", s.vspMustBeOpen, s.withDcrdClient(dcrd), s.broadcastTicket, s.vspAuth, s.feeAddress)
api.POST("/ticketstatus", s.withDcrdClient(dcrd), s.vspAuth, s.ticketStatus)
api.POST("/payfee", s.vspMustBeOpen, s.withDcrdClient(dcrd), s.vspAuth, s.payFee)
api.POST("/setvotechoices", s.withDcrdClient(dcrd), s.withWalletClients(wallets), s.vspAuth, s.setVoteChoices)
api.GET("/vspinfo", w.vspInfo)
api.POST("/setaltsignaddr", w.vspMustBeOpen, w.withDcrdClient(dcrd), w.broadcastTicket, w.vspAuth, w.setAltSignAddr)
api.POST("/feeaddress", w.vspMustBeOpen, w.withDcrdClient(dcrd), w.broadcastTicket, w.vspAuth, w.feeAddress)
api.POST("/ticketstatus", w.withDcrdClient(dcrd), w.vspAuth, w.ticketStatus)
api.POST("/payfee", w.vspMustBeOpen, w.withDcrdClient(dcrd), w.vspAuth, w.payFee)
api.POST("/setvotechoices", w.withDcrdClient(dcrd), w.withWalletClients(wallets), w.vspAuth, w.setVoteChoices)
// Website routes.
router.GET("", s.homepage)
router.GET("", w.homepage)
login := router.Group("/admin").Use(
s.withSession(cookieStore),
w.withSession(cookieStore),
)
// Limit login attempts to 3 per second.
loginRateLmiter := rateLimit(3, func(c *gin.Context) {
s.log.Warnf("Login rate limit exceeded by %s", c.ClientIP())
w.log.Warnf("Login rate limit exceeded by %s", c.ClientIP())
c.HTML(http.StatusTooManyRequests, "login.html", gin.H{
"WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg,
"WebApiCache": w.cache.getData(),
"WebApiCfg": w.cfg,
"FailedLoginMsg": "Rate limit exceeded",
})
})
login.POST("", loginRateLmiter, s.adminLogin)
login.POST("", loginRateLmiter, w.adminLogin)
admin := router.Group("/admin").Use(
s.withWalletClients(wallets), s.withSession(cookieStore), s.requireAdmin,
w.withWalletClients(wallets), w.withSession(cookieStore), w.requireAdmin,
)
admin.GET("", s.withDcrdClient(dcrd), s.adminPage)
admin.POST("/ticket", s.withDcrdClient(dcrd), s.ticketSearch)
admin.GET("/backup", s.downloadDatabaseBackup)
admin.POST("/logout", s.adminLogout)
admin.GET("", w.withDcrdClient(dcrd), w.adminPage)
admin.POST("/ticket", w.withDcrdClient(dcrd), w.ticketSearch)
admin.GET("/backup", w.downloadDatabaseBackup)
admin.POST("/logout", w.adminLogout)
// Require Basic HTTP Auth on /admin/status endpoint.
basic := router.Group("/admin").Use(
s.withDcrdClient(dcrd), s.withWalletClients(wallets), gin.BasicAuth(gin.Accounts{
"admin": s.cfg.AdminPass,
w.withDcrdClient(dcrd), w.withWalletClients(wallets), gin.BasicAuth(gin.Accounts{
"admin": w.cfg.AdminPass,
}),
)
basic.GET("/status", s.statusJSON)
basic.GET("/status", w.statusJSON)
return router
}
@ -288,15 +288,15 @@ func (s *server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
// sendJSONResponse serializes the provided response, signs it, and sends the
// response to the client with a 200 OK status. Returns the seralized response
// and the signature.
func (s *server) sendJSONResponse(resp any, c *gin.Context) (string, string) {
func (w *WebAPI) sendJSONResponse(resp any, c *gin.Context) (string, string) {
dec, err := json.Marshal(resp)
if err != nil {
s.log.Errorf("JSON marshal error: %v", err)
s.sendError(types.ErrInternalError, c)
w.log.Errorf("JSON marshal error: %v", err)
w.sendError(types.ErrInternalError, c)
return "", ""
}
sig := ed25519.Sign(s.signPrivKey, dec)
sig := ed25519.Sign(w.signPrivKey, dec)
sigStr := base64.StdEncoding.EncodeToString(sig)
c.Writer.Header().Set("VSP-Server-Signature", sigStr)
@ -307,14 +307,14 @@ func (s *server) sendJSONResponse(resp any, c *gin.Context) (string, string) {
// sendError sends an error response with the provided error code and the
// default message for that code.
func (s *server) sendError(e types.ErrorCode, c *gin.Context) {
func (w *WebAPI) sendError(e types.ErrorCode, c *gin.Context) {
msg := e.DefaultMessage()
s.sendErrorWithMsg(msg, e, c)
w.sendErrorWithMsg(msg, e, c)
}
// sendErrorWithMsg sends an error response with the provided error code and
// message.
func (s *server) sendErrorWithMsg(msg string, e types.ErrorCode, c *gin.Context) {
func (w *WebAPI) sendErrorWithMsg(msg string, e types.ErrorCode, c *gin.Context) {
status := e.HTTPStatus()
resp := types.ErrorResponse{
@ -325,9 +325,9 @@ func (s *server) sendErrorWithMsg(msg string, e types.ErrorCode, c *gin.Context)
// Try to sign the error response. If it fails, send it without a signature.
dec, err := json.Marshal(resp)
if err != nil {
s.log.Warnf("Sending error response without signature: %v", err)
w.log.Warnf("Sending error response without signature: %v", err)
} else {
sig := ed25519.Sign(s.signPrivKey, dec)
sig := ed25519.Sign(w.signPrivKey, dec)
c.Writer.Header().Set("VSP-Server-Signature", base64.StdEncoding.EncodeToString(sig))
}