From a7bb0cd9d70f7b36b5515876828b3aa44ae1d049 Mon Sep 17 00:00:00 2001 From: jholdstock Date: Fri, 15 Sep 2023 09:53:49 +0100 Subject: [PATCH] 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). --- internal/webapi/admin.go | 84 ++++++++-------- internal/webapi/getfeeaddress.go | 78 +++++++------- internal/webapi/homepage.go | 6 +- internal/webapi/middleware.go | 134 ++++++++++++------------- internal/webapi/payfee.go | 120 +++++++++++----------- internal/webapi/setaltsignaddr.go | 52 +++++----- internal/webapi/setaltsignaddr_test.go | 4 +- internal/webapi/setvotechoices.go | 68 ++++++------- internal/webapi/ticketstatus.go | 18 ++-- internal/webapi/vspinfo.go | 16 +-- internal/webapi/webapi.go | 80 +++++++-------- 11 files changed, 330 insertions(+), 330 deletions(-) diff --git a/internal/webapi/admin.go b/internal/webapi/admin.go index a166599..6fe29a2 100644 --- a/internal/webapi/admin.go +++ b/internal/webapi/admin.go @@ -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 } diff --git a/internal/webapi/getfeeaddress.go b/internal/webapi/getfeeaddress.go index 46986c7..cf1ad6f 100644 --- a/internal/webapi/getfeeaddress.go +++ b/internal/webapi/getfeeaddress.go @@ -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, diff --git a/internal/webapi/homepage.go b/internal/webapi/homepage.go index 5401e31..e8c26d0 100644 --- a/internal/webapi/homepage.go +++ b/internal/webapi/homepage.go @@ -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, }) } diff --git a/internal/webapi/middleware.go b/internal/webapi/middleware.go index 85b6a8a..14b08bc 100644 --- a/internal/webapi/middleware.go +++ b/internal/webapi/middleware.go @@ -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 } diff --git a/internal/webapi/payfee.go b/internal/webapi/payfee.go index 78059ca..5928359 100644 --- a/internal/webapi/payfee.go +++ b/internal/webapi/payfee.go @@ -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) } } diff --git a/internal/webapi/setaltsignaddr.go b/internal/webapi/setaltsignaddr.go index 2abe0a4..7d4a6c8 100644 --- a/internal/webapi/setaltsignaddr.go +++ b/internal/webapi/setaltsignaddr.go @@ -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) } diff --git a/internal/webapi/setaltsignaddr_test.go b/internal/webapi/setaltsignaddr_test.go index bde951f..d95354d 100644 --- a/internal/webapi/setaltsignaddr_test.go +++ b/internal/webapi/setaltsignaddr_test.go @@ -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, diff --git a/internal/webapi/setvotechoices.go b/internal/webapi/setvotechoices.go index 3863dc5..582780a 100644 --- a/internal/webapi/setvotechoices.go +++ b/internal/webapi/setvotechoices.go @@ -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) } } diff --git a/internal/webapi/ticketstatus.go b/internal/webapi/ticketstatus.go index 7258321..219bb82 100644 --- a/internal/webapi/ticketstatus.go +++ b/internal/webapi/ticketstatus.go @@ -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, diff --git a/internal/webapi/vspinfo.go b/internal/webapi/vspinfo.go index d0c96d1..4f4568d 100644 --- a/internal/webapi/vspinfo.go +++ b/internal/webapi/vspinfo.go @@ -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, diff --git a/internal/webapi/webapi.go b/internal/webapi/webapi.go index 89bc450..ecbb6c3 100644 --- a/internal/webapi/webapi.go +++ b/internal/webapi/webapi.go @@ -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)) }