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

View File

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

View File

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

View File

@ -21,7 +21,7 @@ import (
) )
// payFee is the handler for "POST /api/v3/payfee". // 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" const funcName = "payFee"
// Get values which have been added to context by middleware. // 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) dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey) dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil { if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error)) w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
reqBytes := c.MustGet(requestBytesKey).([]byte) reqBytes := c.MustGet(requestBytesKey).([]byte)
if !knownTicket { if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP()) w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c) w.sendError(types.ErrUnknownTicket, c)
return return
} }
var request types.PayFeeRequest var request types.PayFeeRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c) w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return return
} }
@ -53,49 +53,49 @@ func (s *server) payFee(c *gin.Context) {
if ticket.FeeTxStatus == database.FeeReceieved || if ticket.FeeTxStatus == database.FeeReceieved ||
ticket.FeeTxStatus == database.FeeBroadcast || ticket.FeeTxStatus == database.FeeBroadcast ||
ticket.FeeTxStatus == database.FeeConfirmed { 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) funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeAlreadyReceived, c) w.sendError(types.ErrFeeAlreadyReceived, c)
return return
} }
// Get ticket details. // Get ticket details.
rawTicket, err := dcrdClient.GetRawTransaction(ticket.Hash) rawTicket, err := dcrdClient.GetRawTransaction(ticket.Hash)
if err != nil { if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err) w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
// Ensure this ticket is eligible to vote at some point in the future. // 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 { if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err) w.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
if !canVote { 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) funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrTicketCannotVote, c) w.sendError(types.ErrTicketCannotVote, c)
return return
} }
// Respond early if the fee for this ticket is expired. // Respond early if the fee for this ticket is expired.
if ticket.FeeExpired() { 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) funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeExpired, c) w.sendError(types.ErrFeeExpired, c)
return return
} }
// Validate VotingKey. // Validate VotingKey.
votingKey := request.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 { 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) funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidPrivKey, c) w.sendError(types.ErrInvalidPrivKey, c)
return return
} }
@ -103,10 +103,10 @@ func (s *server) payFee(c *gin.Context) {
// the ticket should still be registered. // the ticket should still be registered.
validVoteChoices := true 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 { if err != nil {
validVoteChoices = false 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) funcName, c.ClientIP(), ticket.Hash, err)
} }
@ -114,7 +114,7 @@ func (s *server) payFee(c *gin.Context) {
err = validTreasuryPolicy(request.TreasuryPolicy) err = validTreasuryPolicy(request.TreasuryPolicy)
if err != nil { if err != nil {
validTreasury = false 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) funcName, c.ClientIP(), ticket.Hash, err)
} }
@ -122,33 +122,33 @@ func (s *server) payFee(c *gin.Context) {
err = validTSpendPolicy(request.TSpendPolicy) err = validTSpendPolicy(request.TSpendPolicy)
if err != nil { if err != nil {
validTSpend = false 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) funcName, c.ClientIP(), ticket.Hash, err)
} }
// Validate FeeTx. // Validate FeeTx.
feeTx, err := decodeTransaction(request.FeeTx) feeTx, err := decodeTransaction(request.FeeTx)
if err != nil { 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) funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidFeeTx, c) w.sendError(types.ErrInvalidFeeTx, c)
return return
} }
err = blockchain.CheckTransactionSanity(feeTx, uint64(s.cfg.Network.MaxTxSize)) err = blockchain.CheckTransactionSanity(feeTx, uint64(w.cfg.Network.MaxTxSize))
if err != nil { 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) funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(types.ErrInvalidFeeTx, c) w.sendError(types.ErrInvalidFeeTx, c)
return return
} }
// Decode fee address to get its payment script details. // 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 { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
@ -166,9 +166,9 @@ func (s *server) payFee(c *gin.Context) {
// Confirm a fee payment was found. // Confirm a fee payment was found.
if feePaid == 0 { 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()) 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), fmt.Sprintf("feetx did not include any payments for fee address %s", ticket.FeeAddress),
types.ErrInvalidFeeTx, c) types.ErrInvalidFeeTx, c)
return return
@ -177,19 +177,19 @@ func (s *server) payFee(c *gin.Context) {
// Confirm fee payment is equal to or larger than the minimum expected. // Confirm fee payment is equal to or larger than the minimum expected.
minFee := dcrutil.Amount(ticket.FeeAmount) minFee := dcrutil.Amount(ticket.FeeAmount)
if feePaid < minFee { 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) funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
s.sendError(types.ErrFeeTooSmall, c) w.sendError(types.ErrFeeTooSmall, c)
return return
} }
// Decode the provided voting WIF to get its voting rights script. // Decode the provided voting WIF to get its voting rights script.
pkHash := stdaddr.Hash160(votingWIF.PubKey()) pkHash := stdaddr.Hash160(votingWIF.PubKey())
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, s.cfg.Network) wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, w.cfg.Network)
if err != nil { 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) funcName, ticket.Hash, c.ClientIP(), err)
s.sendError(types.ErrInvalidPrivKey, c) w.sendError(types.ErrInvalidPrivKey, c)
return return
} }
@ -198,9 +198,9 @@ func (s *server) payFee(c *gin.Context) {
// Decode ticket transaction to get its voting rights script. // Decode ticket transaction to get its voting rights script.
ticketTx, err := decodeTransaction(rawTicket.Hex) ticketTx, err := decodeTransaction(rawTicket.Hex)
if err != nil { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
@ -210,9 +210,9 @@ func (s *server) payFee(c *gin.Context) {
// Ensure provided voting WIF matches the actual voting address of the // Ensure provided voting WIF matches the actual voting address of the
// ticket. Both script and script version should match. // ticket. Both script and script version should match.
if actualScriptVer != wantScriptVer || !bytes.Equal(actualScript, wantScript) { 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) 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) types.ErrInvalidPrivKey, c)
return return
} }
@ -238,35 +238,35 @@ func (s *server) payFee(c *gin.Context) {
ticket.TreasuryPolicy = request.TreasuryPolicy ticket.TreasuryPolicy = request.TreasuryPolicy
} }
err = s.db.UpdateTicket(ticket) err = w.db.UpdateTicket(ticket)
if err != nil { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return 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) funcName, minFee, feePaid, ticket.Hash)
if ticket.Confirmed { if ticket.Confirmed {
err = dcrdClient.SendRawTransaction(request.FeeTx) err = dcrdClient.SendRawTransaction(request.FeeTx)
if err != nil { 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) funcName, ticket.Hash, err)
ticket.FeeTxStatus = database.FeeError ticket.FeeTxStatus = database.FeeError
// Send the client an explicit error if the issue is unknown outputs. // Send the client an explicit error if the issue is unknown outputs.
if strings.Contains(err.Error(), rpc.ErrUnknownOutputs) { if strings.Contains(err.Error(), rpc.ErrUnknownOutputs) {
s.sendError(types.ErrCannotBroadcastFeeUnknownOutputs, c) w.sendError(types.ErrCannotBroadcastFeeUnknownOutputs, c)
} else { } else {
s.sendError(types.ErrCannotBroadcastFee, c) w.sendError(types.ErrCannotBroadcastFee, c)
} }
err = s.db.UpdateTicket(ticket) err = w.db.UpdateTicket(ticket)
if err != nil { 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) funcName, ticket.Hash, err)
} }
@ -275,26 +275,26 @@ func (s *server) payFee(c *gin.Context) {
ticket.FeeTxStatus = database.FeeBroadcast ticket.FeeTxStatus = database.FeeBroadcast
err = s.db.UpdateTicket(ticket) err = w.db.UpdateTicket(ticket)
if err != nil { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return 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) funcName, ticket.Hash, ticket.FeeTxHash)
} }
// Send success response to client. // Send success response to client.
resp, respSig := s.sendJSONResponse(types.PayFeeResponse{ resp, respSig := w.sendJSONResponse(types.PayFeeResponse{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
Request: reqBytes, Request: reqBytes,
}, c) }, c)
// Store a record of the vote choice change. // Store a record of the vote choice change.
err = s.db.SaveVoteChange( err = w.db.SaveVoteChange(
ticket.Hash, ticket.Hash,
database.VoteChangeRecord{ database.VoteChangeRecord{
Request: string(reqBytes), Request: string(reqBytes),
@ -303,6 +303,6 @@ func (s *server) payFee(c *gin.Context) {
ResponseSignature: respSig, ResponseSignature: respSig,
}) })
if err != nil { 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". // 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" const funcName = "setAltSignAddr"
@ -34,72 +34,72 @@ func (s *server) setAltSignAddr(c *gin.Context) {
dcrdClient := c.MustGet(dcrdKey).(node) dcrdClient := c.MustGet(dcrdKey).(node)
dcrdErr := c.MustGet(dcrdErrorKey) dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil { if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error)) w.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
reqBytes := c.MustGet(requestBytesKey).([]byte) reqBytes := c.MustGet(requestBytesKey).([]byte)
var request types.SetAltSignAddrRequest var request types.SetAltSignAddrRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c) w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return return
} }
altSignAddr, ticketHash := request.AltSignAddress, request.TicketHash altSignAddr, ticketHash := request.AltSignAddress, request.TicketHash
currentData, err := s.db.AltSignAddrData(ticketHash) currentData, err := w.db.AltSignAddrData(ticketHash)
if err != nil { if err != nil {
s.log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err) w.log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
if currentData != nil { if currentData != nil {
msg := "alternate sign address data already exists" msg := "alternate sign address data already exists"
s.log.Warnf("%s: %s (ticketHash=%s)", funcName, msg, ticketHash) w.log.Warnf("%s: %s (ticketHash=%s)", funcName, msg, ticketHash)
s.sendErrorWithMsg(msg, types.ErrBadRequest, c) w.sendErrorWithMsg(msg, types.ErrBadRequest, c)
return return
} }
// Fail fast if the pubkey doesn't decode properly. // 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 { if err != nil {
s.log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err) w.log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c) w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return return
} }
if _, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok { if _, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok {
s.log.Warnf("%s: Alt sign address is unexpected type (clientIP=%s, type=%T)", funcName, c.ClientIP(), addr) w.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.sendErrorWithMsg("wrong type for alternate signing address", types.ErrBadRequest, c)
return return
} }
// Get ticket details. // Get ticket details.
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash) rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
if err != nil { if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err) w.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
// Ensure this ticket is eligible to vote at some point in the future. // 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 { if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err) w.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
if !canVote { 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) funcName, c.ClientIP(), ticketHash)
s.sendError(types.ErrTicketCannotVote, c) w.sendError(types.ErrTicketCannotVote, c)
return return
} }
// Send success response to client. // Send success response to client.
resp, respSig := s.sendJSONResponse(types.SetAltSignAddrResponse{ resp, respSig := w.sendJSONResponse(types.SetAltSignAddrResponse{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
Request: reqBytes, Request: reqBytes,
}, c) }, c)
@ -112,12 +112,12 @@ func (s *server) setAltSignAddr(c *gin.Context) {
RespSig: respSig, RespSig: respSig,
} }
err = s.db.InsertAltSignAddr(ticketHash, data) err = w.db.InsertAltSignAddr(ticketHash, data)
if err != nil { 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) funcName, ticketHash, err)
return 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())) seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
feeXPub = "feexpub" feeXPub = "feexpub"
maxVoteChangeRecords = 3 maxVoteChangeRecords = 3
api *server api *WebAPI
) )
// randBytes returns a byte slice of size n filled with random bytes. // 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)) panic(fmt.Errorf("error opening test database: %w", err))
} }
api = &server{ api = &WebAPI{
cfg: cfg, cfg: cfg,
signPrivKey: signPrivKey, signPrivKey: signPrivKey,
db: db, db: db,

View File

@ -17,7 +17,7 @@ import (
) )
// setVoteChoices is the handler for "POST /api/v3/setvotechoices". // 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" const funcName = "setVoteChoices"
// Get values which have been added to context by middleware. // 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 // If we cannot set the vote choices on at least one voting wallet right
// now, don't update the database, just return an error. // now, don't update the database, just return an error.
if len(walletClients) == 0 { if len(walletClients) == 0 {
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
if !knownTicket { if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP()) w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c) w.sendError(types.ErrUnknownTicket, c)
return return
} }
if ticket.FeeTxStatus == database.NoFee { 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) funcName, c.ClientIP(), ticket.Hash)
s.sendError(types.ErrFeeNotReceived, c) w.sendError(types.ErrFeeNotReceived, c)
return return
} }
// Only allow vote choices to be updated for mempool/immature/live tickets. // Only allow vote choices to be updated for mempool/immature/live tickets.
if ticket.Outcome != "" { 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) 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) types.ErrTicketCannotVote, c)
return return
} }
var request types.SetVoteChoicesRequest var request types.SetVoteChoicesRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c) w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return return
} }
// Return an error if this request has a timestamp older than any previous // Return an error if this request has a timestamp older than any previous
// vote change requests. This is to prevent requests from being replayed. // 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 { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
@ -78,43 +78,43 @@ func (s *server) setVoteChoices(c *gin.Context) {
} }
err := json.Unmarshal([]byte(change.Request), &prevReq) err := json.Unmarshal([]byte(change.Request), &prevReq)
if err != nil { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
if request.Timestamp <= prevReq.Timestamp { 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)", "than %d (ticketHash=%s)",
funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash) funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash)
s.sendError(types.ErrInvalidTimestamp, c) w.sendError(types.ErrInvalidTimestamp, c)
return return
} }
} }
// Validate vote choices (consensus, tspend policy and treasury policy). // 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 { 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) funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c) w.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
return return
} }
err = validTreasuryPolicy(request.TreasuryPolicy) err = validTreasuryPolicy(request.TreasuryPolicy)
if err != nil { 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) funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c) w.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
} }
err = validTSpendPolicy(request.TSpendPolicy) err = validTSpendPolicy(request.TSpendPolicy)
if err != nil { 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) 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 // 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 ticket.TreasuryPolicy[newTreasuryKey] = newChoice
} }
err = s.db.UpdateTicket(ticket) err = w.db.UpdateTicket(ticket)
if err != nil { 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) funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
@ -152,7 +152,7 @@ func (s *server) setVoteChoices(c *gin.Context) {
for agenda, choice := range ticket.VoteChoices { for agenda, choice := range ticket.VoteChoices {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash) err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil { 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) funcName, walletClient.String(), ticket.Hash, err)
} }
} }
@ -161,7 +161,7 @@ func (s *server) setVoteChoices(c *gin.Context) {
for tspend, policy := range ticket.TSpendPolicy { for tspend, policy := range ticket.TSpendPolicy {
err = walletClient.SetTSpendPolicy(tspend, policy, ticket.Hash) err = walletClient.SetTSpendPolicy(tspend, policy, ticket.Hash)
if err != nil { 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) funcName, walletClient.String(), ticket.Hash, err)
} }
} }
@ -170,23 +170,23 @@ func (s *server) setVoteChoices(c *gin.Context) {
for key, policy := range ticket.TreasuryPolicy { for key, policy := range ticket.TreasuryPolicy {
err = walletClient.SetTreasuryPolicy(key, policy, ticket.Hash) err = walletClient.SetTreasuryPolicy(key, policy, ticket.Hash)
if err != nil { 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) 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. // Send success response to client.
resp, respSig := s.sendJSONResponse(types.SetVoteChoicesResponse{ resp, respSig := w.sendJSONResponse(types.SetVoteChoicesResponse{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
Request: reqBytes, Request: reqBytes,
}, c) }, c)
// Store a record of the vote choice change. // Store a record of the vote choice change.
err = s.db.SaveVoteChange( err = w.db.SaveVoteChange(
ticket.Hash, ticket.Hash,
database.VoteChangeRecord{ database.VoteChangeRecord{
Request: string(reqBytes), Request: string(reqBytes),
@ -195,6 +195,6 @@ func (s *server) setVoteChoices(c *gin.Context) {
ResponseSignature: respSig, ResponseSignature: respSig,
}) })
if err != nil { 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". // 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" const funcName = "ticketStatus"
// Get values which have been added to context by middleware. // 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) reqBytes := c.MustGet(requestBytesKey).([]byte)
if !knownTicket { if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP()) w.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(types.ErrUnknownTicket, c) w.sendError(types.ErrUnknownTicket, c)
return return
} }
var request types.TicketStatusRequest var request types.TicketStatusRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) w.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c) w.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return return
} }
// Get altSignAddress from database // Get altSignAddress from database
altSignAddrData, err := s.db.AltSignAddrData(ticket.Hash) altSignAddrData, err := w.db.AltSignAddrData(ticket.Hash)
if err != nil { if err != nil {
s.log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err) w.log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(types.ErrInternalError, c) w.sendError(types.ErrInternalError, c)
return return
} }
@ -48,7 +48,7 @@ func (s *server) ticketStatus(c *gin.Context) {
altSignAddr = altSignAddrData.AltSignAddr altSignAddr = altSignAddrData.AltSignAddr
} }
s.sendJSONResponse(types.TicketStatusResponse{ w.sendJSONResponse(types.TicketStatusResponse{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
Request: reqBytes, Request: reqBytes,
TicketConfirmed: ticket.Confirmed, TicketConfirmed: ticket.Confirmed,

View File

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

View File

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