Remove global vars from webapi package.
This commit is contained in:
parent
4d4f9c8ca0
commit
37d51df546
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -109,7 +109,7 @@ func 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 statusJSON(c *gin.Context) {
|
||||
func (s *Server) statusJSON(c *gin.Context) {
|
||||
httpStatus := http.StatusOK
|
||||
|
||||
wallets := walletStatus(c)
|
||||
@ -141,10 +141,10 @@ func statusJSON(c *gin.Context) {
|
||||
}
|
||||
|
||||
// adminPage is the handler for "GET /admin".
|
||||
func adminPage(c *gin.Context) {
|
||||
func (s *Server) adminPage(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "admin.html", gin.H{
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WebApiCfg": s.cfg,
|
||||
"WalletStatus": walletStatus(c),
|
||||
"DcrdStatus": dcrdStatus(c),
|
||||
})
|
||||
@ -152,24 +152,24 @@ func adminPage(c *gin.Context) {
|
||||
|
||||
// ticketSearch is the handler for "POST /admin/ticket". The hash param will be
|
||||
// used to retrieve a ticket from the database.
|
||||
func ticketSearch(c *gin.Context) {
|
||||
func (s *Server) ticketSearch(c *gin.Context) {
|
||||
hash := c.PostForm("hash")
|
||||
|
||||
ticket, found, err := db.GetTicketByHash(hash)
|
||||
ticket, found, err := s.db.GetTicketByHash(hash)
|
||||
if err != nil {
|
||||
log.Errorf("db.GetTicketByHash error (ticketHash=%s): %v", hash, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting ticket from db")
|
||||
return
|
||||
}
|
||||
|
||||
voteChanges, err := db.GetVoteChanges(hash)
|
||||
voteChanges, err := s.db.GetVoteChanges(hash)
|
||||
if err != nil {
|
||||
log.Errorf("db.GetVoteChanges error (ticketHash=%s): %v", hash, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting vote changes from db")
|
||||
return
|
||||
}
|
||||
|
||||
altSignAddrData, err := db.AltSignAddrData(hash)
|
||||
altSignAddrData, err := s.db.AltSignAddrData(hash)
|
||||
if err != nil {
|
||||
log.Errorf("db.AltSignAddrData error (ticketHash=%s): %v", hash, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting alt sig from db")
|
||||
@ -183,10 +183,10 @@ func ticketSearch(c *gin.Context) {
|
||||
Ticket: ticket,
|
||||
AltSignAddrData: altSignAddrData,
|
||||
VoteChanges: voteChanges,
|
||||
MaxVoteChanges: cfg.MaxVoteChangeRecords,
|
||||
MaxVoteChanges: s.cfg.MaxVoteChangeRecords,
|
||||
},
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WebApiCfg": s.cfg,
|
||||
"WalletStatus": walletStatus(c),
|
||||
"DcrdStatus": dcrdStatus(c),
|
||||
})
|
||||
@ -194,14 +194,14 @@ func ticketSearch(c *gin.Context) {
|
||||
|
||||
// adminLogin is the handler for "POST /admin". If a valid password is provided,
|
||||
// the current session will be authenticated as an admin.
|
||||
func adminLogin(c *gin.Context) {
|
||||
func (s *Server) adminLogin(c *gin.Context) {
|
||||
password := c.PostForm("password")
|
||||
|
||||
if password != cfg.AdminPass {
|
||||
if password != s.cfg.AdminPass {
|
||||
log.Warnf("Failed login attempt from %s", c.ClientIP())
|
||||
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WebApiCfg": s.cfg,
|
||||
"IncorrectPassword": true,
|
||||
})
|
||||
return
|
||||
@ -212,14 +212,14 @@ func adminLogin(c *gin.Context) {
|
||||
|
||||
// adminLogout is the handler for "POST /admin/logout". The current session will
|
||||
// have its admin authentication removed.
|
||||
func adminLogout(c *gin.Context) {
|
||||
func (s *Server) adminLogout(c *gin.Context) {
|
||||
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 downloadDatabaseBackup(c *gin.Context) {
|
||||
err := db.BackupDB(c.Writer)
|
||||
func (s *Server) downloadDatabaseBackup(c *gin.Context) {
|
||||
err := s.db.BackupDB(c.Writer)
|
||||
if err != nil {
|
||||
log.Errorf("Error backing up database: %v", err)
|
||||
// Don't write any http body here because Content-Length has already
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,7 +6,6 @@ package webapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
@ -45,12 +44,12 @@ func getCache() apiCache {
|
||||
|
||||
// initCache creates the struct which holds the cached VSP stats, and
|
||||
// initializes it with static values.
|
||||
func initCache() {
|
||||
func initCache(signPubKey string) {
|
||||
cacheMtx.Lock()
|
||||
defer cacheMtx.Unlock()
|
||||
|
||||
cache = apiCache{
|
||||
PubKey: base64.StdEncoding.EncodeToString(signPubKey),
|
||||
PubKey: signPubKey,
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +84,7 @@ func updateCache(ctx context.Context, db *database.VspDatabase,
|
||||
return errors.New("dcr node reports a network ticket pool size of zero")
|
||||
}
|
||||
|
||||
clients, failedConnections := wallets.Clients(ctx, cfg.NetParams)
|
||||
clients, failedConnections := wallets.Clients(ctx, netParams)
|
||||
if len(clients) == 0 {
|
||||
log.Error("Could not connect to any wallets")
|
||||
} else if len(failedConnections) > 0 {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -23,16 +23,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 getNewFeeAddress(db *database.VspDatabase, addrGen *addressGenerator) (string, uint32, error) {
|
||||
func (s *Server) getNewFeeAddress() (string, uint32, error) {
|
||||
addrMtx.Lock()
|
||||
defer addrMtx.Unlock()
|
||||
|
||||
addr, idx, err := addrGen.NextAddress()
|
||||
addr, idx, err := s.addrGen.NextAddress()
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
err = db.SetLastAddressIndex(idx)
|
||||
err = s.db.SetLastAddressIndex(idx)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
@ -42,7 +42,7 @@ func getNewFeeAddress(db *database.VspDatabase, addrGen *addressGenerator) (stri
|
||||
|
||||
// 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 getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
|
||||
func (s *Server) getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
|
||||
bestBlock, err := dcrdClient.GetBestBlockHeader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -64,7 +64,7 @@ func getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
|
||||
}
|
||||
|
||||
fee := txrules.StakePoolTicketFee(sDiff, defaultMinRelayTxFee,
|
||||
int32(bestBlock.Height), cfg.VSPFee, cfg.NetParams, isDCP0010Active)
|
||||
int32(bestBlock.Height), s.cfg.VSPFee, s.cfg.NetParams, isDCP0010Active)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -72,7 +72,7 @@ func getCurrentFee(dcrdClient *rpc.DcrdRPC) (dcrutil.Amount, error) {
|
||||
}
|
||||
|
||||
// feeAddress is the handler for "POST /api/v3/feeaddress".
|
||||
func feeAddress(c *gin.Context) {
|
||||
func (s *Server) feeAddress(c *gin.Context) {
|
||||
|
||||
const funcName = "feeAddress"
|
||||
|
||||
@ -84,20 +84,20 @@ func feeAddress(c *gin.Context) {
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
reqBytes := c.MustGet(requestBytesKey).([]byte)
|
||||
|
||||
if cfg.VspClosed {
|
||||
sendError(errVspClosed, c)
|
||||
if s.cfg.VspClosed {
|
||||
s.sendError(errVspClosed, c)
|
||||
return
|
||||
}
|
||||
|
||||
var request feeAddressRequest
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ func feeAddress(c *gin.Context) {
|
||||
ticket.FeeTxStatus == database.FeeConfirmed) {
|
||||
log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendError(errFeeAlreadyReceived, c)
|
||||
s.sendError(errFeeAlreadyReceived, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -118,21 +118,21 @@ func feeAddress(c *gin.Context) {
|
||||
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure this ticket is eligible to vote at some point in the future.
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticketHash, cfg.NetParams)
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticketHash, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.CanTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
if !canVote {
|
||||
log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticketHash)
|
||||
sendError(errTicketCannotVote, c)
|
||||
s.sendError(errTicketCannotVote, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -142,26 +142,26 @@ func 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 := getCurrentFee(dcrdClient)
|
||||
newFee, err := s.getCurrentFee(dcrdClient)
|
||||
if err != nil {
|
||||
log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
ticket.FeeExpiration = now.Add(feeAddressExpiration).Unix()
|
||||
ticket.FeeAmount = int64(newFee)
|
||||
|
||||
err = db.UpdateTicket(ticket)
|
||||
err = s.db.UpdateTicket(ticket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)",
|
||||
funcName, newFee, ticket.Hash)
|
||||
}
|
||||
sendJSONResponse(feeAddressResponse{
|
||||
s.sendJSONResponse(feeAddressResponse{
|
||||
Timestamp: now.Unix(),
|
||||
Request: reqBytes,
|
||||
FeeAddress: ticket.FeeAddress,
|
||||
@ -175,17 +175,17 @@ func feeAddress(c *gin.Context) {
|
||||
// Beyond this point we are processing a new ticket which the VSP has not
|
||||
// seen before.
|
||||
|
||||
fee, err := getCurrentFee(dcrdClient)
|
||||
fee, err := s.getCurrentFee(dcrdClient)
|
||||
if err != nil {
|
||||
log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
newAddress, newAddressIdx, err := getNewFeeAddress(db, addrGen)
|
||||
newAddress, newAddressIdx, err := s.getNewFeeAddress()
|
||||
if err != nil {
|
||||
log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -213,10 +213,10 @@ func feeAddress(c *gin.Context) {
|
||||
FeeTxStatus: database.NoFee,
|
||||
}
|
||||
|
||||
err = db.InsertNewTicket(dbTicket)
|
||||
err = s.db.InsertNewTicket(dbTicket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ func feeAddress(c *gin.Context) {
|
||||
"feeAddr=%s, feeAmt=%s, ticketHash=%s)",
|
||||
funcName, confirmed, newAddressIdx, newAddress, fee, ticketHash)
|
||||
|
||||
sendJSONResponse(feeAddressResponse{
|
||||
s.sendJSONResponse(feeAddressResponse{
|
||||
Timestamp: now.Unix(),
|
||||
Request: reqBytes,
|
||||
FeeAddress: newAddress,
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/decred/dcrd/dcrutil/v4"
|
||||
"github.com/decred/dcrd/wire"
|
||||
"github.com/decred/vspd/database"
|
||||
"github.com/decred/vspd/rpc"
|
||||
)
|
||||
|
||||
@ -105,8 +106,10 @@ func validPolicyOption(policy string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func validateSignature(hash, commitmentAddress, signature, message string) error {
|
||||
firstErr := dcrutil.VerifyMessage(commitmentAddress, signature, message, cfg.NetParams)
|
||||
func validateSignature(hash, commitmentAddress, signature, message string,
|
||||
db *database.VspDatabase, params *chaincfg.Params) error {
|
||||
|
||||
firstErr := dcrutil.VerifyMessage(commitmentAddress, signature, message, params)
|
||||
if firstErr != nil {
|
||||
// Don't return an error straight away if sig validation fails -
|
||||
// first check if we have an alternate sign address for this ticket.
|
||||
@ -117,7 +120,8 @@ func validateSignature(hash, commitmentAddress, signature, message string) error
|
||||
|
||||
// If we have no alternate sign address, or if validating with the
|
||||
// alt sign addr fails, return an error to the client.
|
||||
if altSigData == nil || dcrutil.VerifyMessage(altSigData.AltSignAddr, signature, message, cfg.NetParams) != nil {
|
||||
if altSigData == nil ||
|
||||
dcrutil.VerifyMessage(altSigData.AltSignAddr, signature, message, params) != nil {
|
||||
return fmt.Errorf("bad signature")
|
||||
}
|
||||
|
||||
@ -167,7 +171,7 @@ func validateTicketHash(hash string) error {
|
||||
|
||||
// getCommitmentAddress gets the commitment address of the provided ticket hash
|
||||
// from the chain.
|
||||
func getCommitmentAddress(hash string, dcrdClient *rpc.DcrdRPC) (string, error) {
|
||||
func getCommitmentAddress(hash string, dcrdClient *rpc.DcrdRPC, params *chaincfg.Params) (string, error) {
|
||||
var commitmentAddress string
|
||||
resp, err := dcrdClient.GetRawTransaction(hash)
|
||||
if err != nil {
|
||||
@ -187,7 +191,7 @@ func getCommitmentAddress(hash string, dcrdClient *rpc.DcrdRPC) (string, error)
|
||||
|
||||
}
|
||||
|
||||
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, cfg.NetParams)
|
||||
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, params)
|
||||
if err != nil {
|
||||
return commitmentAddress, fmt.Errorf("AddrFromSStxPkScrCommitment error: %v", err)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -10,9 +10,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func homepage(c *gin.Context) {
|
||||
func (s *Server) homepage(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "homepage.html", gin.H{
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WebApiCfg": s.cfg,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -53,25 +53,23 @@ func 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 requireAdmin() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := c.MustGet(sessionKey).(*sessions.Session)
|
||||
admin := session.Values["admin"]
|
||||
func (s *Server) 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": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if admin == nil {
|
||||
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": s.cfg,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// withDcrdClient middleware adds a dcrd client to the request context for
|
||||
// downstream handlers to make use of.
|
||||
func withDcrdClient(dcrd rpc.DcrdConnect) gin.HandlerFunc {
|
||||
func withDcrdClient(dcrd rpc.DcrdConnect, cfg Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
client, hostname, err := dcrd.Client(c, cfg.NetParams)
|
||||
// Don't handle the error here, add it to the context and let downstream
|
||||
@ -85,7 +83,7 @@ func 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 withWalletClients(wallets rpc.WalletConnect) gin.HandlerFunc {
|
||||
func withWalletClients(wallets rpc.WalletConnect, cfg Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
clients, failedConnections := wallets.Clients(c, cfg.NetParams)
|
||||
if len(clients) == 0 {
|
||||
@ -116,137 +114,135 @@ func drainAndReplaceBody(req *http.Request) ([]byte, error) {
|
||||
// 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 broadcastTicket() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
const funcName = "broadcastTicket"
|
||||
func (s *Server) broadcastTicket(c *gin.Context) {
|
||||
const funcName = "broadcastTicket"
|
||||
|
||||
// Read request bytes.
|
||||
reqBytes, err := drainAndReplaceBody(c.Request)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
// Read request bytes.
|
||||
reqBytes, err := drainAndReplaceBody(c.Request)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request to ensure ticket hash/hex and parent hex are included.
|
||||
var request struct {
|
||||
TicketHex string `json:"tickethex" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
ParentHex string `json:"parenthex" binding:"required"`
|
||||
}
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
// Parse request to ensure ticket hash/hex and parent hex are included.
|
||||
var request struct {
|
||||
TicketHex string `json:"tickethex" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
ParentHex string `json:"parenthex" binding:"required"`
|
||||
}
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the provided ticket hex is a valid ticket.
|
||||
msgTx, err := decodeTransaction(request.TicketHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
|
||||
sendErrorWithMsg("cannot decode ticket hex", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
// Ensure the provided ticket hex is a valid ticket.
|
||||
msgTx, err := decodeTransaction(request.TicketHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
|
||||
s.sendErrorWithMsg("cannot decode ticket hex", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
err = isValidTicket(msgTx)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), request.TicketHash, err)
|
||||
sendError(errInvalidTicket, c)
|
||||
return
|
||||
}
|
||||
err = isValidTicket(msgTx)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), request.TicketHash, err)
|
||||
s.sendError(errInvalidTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure hex matches hash.
|
||||
if msgTx.TxHash().String() != request.TicketHash {
|
||||
log.Warnf("%s: Ticket hex/hash mismatch (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), request.TicketHash)
|
||||
sendErrorWithMsg("ticket hex does not match hash", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
// Ensure hex matches hash.
|
||||
if msgTx.TxHash().String() != request.TicketHash {
|
||||
log.Warnf("%s: Ticket hex/hash mismatch (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), request.TicketHash)
|
||||
s.sendErrorWithMsg("ticket hex does not match hash", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the provided parent hex is a valid tx.
|
||||
parentTx, err := decodeTransaction(request.ParentHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to decode parent hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
|
||||
sendErrorWithMsg("cannot decode parent hex", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
parentHash := parentTx.TxHash()
|
||||
// Ensure the provided parent hex is a valid tx.
|
||||
parentTx, err := decodeTransaction(request.ParentHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to decode parent hex (ticketHash=%s): %v", funcName, request.TicketHash, err)
|
||||
s.sendErrorWithMsg("cannot decode parent hex", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
parentHash := parentTx.TxHash()
|
||||
|
||||
// Check if local dcrd already knows the parent tx.
|
||||
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
// Check if local dcrd already knows the parent tx.
|
||||
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = dcrdClient.GetRawTransaction(parentHash.String())
|
||||
var e *wsrpc.Error
|
||||
if err == nil {
|
||||
// No error means dcrd already knows the parent tx, nothing to do.
|
||||
} else if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo {
|
||||
// ErrNoTxInfo means local dcrd is not aware of the parent. We have
|
||||
// the hex, so we can broadcast it here.
|
||||
_, err = dcrdClient.GetRawTransaction(parentHash.String())
|
||||
var e *wsrpc.Error
|
||||
if err == nil {
|
||||
// No error means dcrd already knows the parent tx, nothing to do.
|
||||
} else if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo {
|
||||
// ErrNoTxInfo means local dcrd is not aware of the parent. We have
|
||||
// the hex, so we can broadcast it here.
|
||||
|
||||
// Before broadcasting, check that the provided parent hex is
|
||||
// actually the parent of the ticket.
|
||||
var found bool
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
if !txIn.PreviousOutPoint.Hash.IsEqual(&parentHash) {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
break
|
||||
// Before broadcasting, check that the provided parent hex is
|
||||
// actually the parent of the ticket.
|
||||
var found bool
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
if !txIn.PreviousOutPoint.Hash.IsEqual(&parentHash) {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Errorf("%s: Invalid ticket parent (ticketHash=%s)", funcName, request.TicketHash)
|
||||
sendErrorWithMsg("invalid ticket parent", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
log.Errorf("%s: Invalid ticket parent (ticketHash=%s)", funcName, request.TicketHash)
|
||||
s.sendErrorWithMsg("invalid ticket parent", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("%s: Broadcasting parent tx %s (ticketHash=%s)", funcName, parentHash, request.TicketHash)
|
||||
err = dcrdClient.SendRawTransaction(request.ParentHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.SendRawTransaction for parent tx failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
sendError(errCannotBroadcastTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket parent failed (ticketHash=%s): %v",
|
||||
log.Debugf("%s: Broadcasting parent tx %s (ticketHash=%s)", funcName, parentHash, request.TicketHash)
|
||||
err = dcrdClient.SendRawTransaction(request.ParentHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.SendRawTransaction for parent tx failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errCannotBroadcastTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if local dcrd already knows the ticket.
|
||||
_, err = dcrdClient.GetRawTransaction(request.TicketHash)
|
||||
if err == nil {
|
||||
// No error means dcrd already knows the ticket, we are done here.
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket parent failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// ErrNoTxInfo means local dcrd is not aware of the ticket. We have the
|
||||
// hex, so we can broadcast it here.
|
||||
if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo {
|
||||
log.Debugf("%s: Broadcasting ticket (ticketHash=%s)", funcName, request.TicketHash)
|
||||
err = dcrdClient.SendRawTransaction(request.TicketHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.SendRawTransaction for ticket failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
sendError(errCannotBroadcastTicket, c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v",
|
||||
// Check if local dcrd already knows the ticket.
|
||||
_, err = dcrdClient.GetRawTransaction(request.TicketHash)
|
||||
if err == nil {
|
||||
// No error means dcrd already knows the ticket, we are done here.
|
||||
return
|
||||
}
|
||||
|
||||
// ErrNoTxInfo means local dcrd is not aware of the ticket. We have the
|
||||
// hex, so we can broadcast it here.
|
||||
if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo {
|
||||
log.Debugf("%s: Broadcasting ticket (ticketHash=%s)", funcName, request.TicketHash)
|
||||
err = dcrdClient.SendRawTransaction(request.TicketHex)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.SendRawTransaction for ticket failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errCannotBroadcastTicket, c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v",
|
||||
funcName, request.TicketHash, err)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,97 +253,94 @@ func broadcastTicket() gin.HandlerFunc {
|
||||
// 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 vspAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
const funcName = "vspAuth"
|
||||
func (s *Server) vspAuth(c *gin.Context) {
|
||||
const funcName = "vspAuth"
|
||||
|
||||
// Read request bytes.
|
||||
reqBytes, err := drainAndReplaceBody(c.Request)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Add request bytes to request context for downstream handlers to reuse.
|
||||
// Necessary because the request body reader can only be used once.
|
||||
c.Set(requestBytesKey, reqBytes)
|
||||
|
||||
// Parse request and ensure there is a ticket hash included.
|
||||
var request struct {
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
}
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
hash := request.TicketHash
|
||||
|
||||
// Before hitting the db or any RPC, ensure this is a valid ticket hash.
|
||||
err = validateTicketHash(hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this ticket already appears in the database.
|
||||
ticket, ticketFound, err := db.GetTicketByHash(hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.GetTicketByHash error (ticketHash=%s): %v", funcName, hash, err)
|
||||
sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// If the ticket was found in the database, we already know its
|
||||
// commitment address. Otherwise we need to get it from the chain.
|
||||
var commitmentAddress string
|
||||
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
if ticketFound {
|
||||
commitmentAddress = ticket.CommitmentAddress
|
||||
} else {
|
||||
commitmentAddress, err = getCommitmentAddress(hash, dcrdClient)
|
||||
if err != nil {
|
||||
var apiErr *apiError
|
||||
if errors.Is(err, apiErr) {
|
||||
sendError(errInvalidTicket, c)
|
||||
} else {
|
||||
sendError(errInternalError, c)
|
||||
}
|
||||
log.Errorf("%s: (clientIP: %s, ticketHash: %s): %v", funcName, c.ClientIP(), hash, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure a signature is provided.
|
||||
signature := c.GetHeader("VSP-Client-Signature")
|
||||
if signature == "" {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg("no VSP-Client-Signature header", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request signature to ensure ticket ownership.
|
||||
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes))
|
||||
if err != nil {
|
||||
log.Errorf("%s: Bad signature (clientIP=%s, ticketHash=%s): %v", funcName, err)
|
||||
sendError(errBadSignature, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Add ticket information to context so downstream handlers don't need
|
||||
// to access the db for it.
|
||||
c.Set(ticketKey, ticket)
|
||||
c.Set(knownTicketKey, ticketFound)
|
||||
c.Set(commitmentAddressKey, commitmentAddress)
|
||||
// Read request bytes.
|
||||
reqBytes, err := drainAndReplaceBody(c.Request)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Error reading request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Add request bytes to request context for downstream handlers to reuse.
|
||||
// Necessary because the request body reader can only be used once.
|
||||
c.Set(requestBytesKey, reqBytes)
|
||||
|
||||
// Parse request and ensure there is a ticket hash included.
|
||||
var request struct {
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
}
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
hash := request.TicketHash
|
||||
|
||||
// Before hitting the db or any RPC, ensure this is a valid ticket hash.
|
||||
err = validateTicketHash(hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this ticket already appears in the database.
|
||||
ticket, ticketFound, err := s.db.GetTicketByHash(hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.GetTicketByHash error (ticketHash=%s): %v", funcName, hash, err)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// If the ticket was found in the database, we already know its
|
||||
// commitment address. Otherwise we need to get it from the chain.
|
||||
var commitmentAddress string
|
||||
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
if ticketFound {
|
||||
commitmentAddress = ticket.CommitmentAddress
|
||||
} else {
|
||||
commitmentAddress, err = getCommitmentAddress(hash, dcrdClient, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
var apiErr *apiError
|
||||
if errors.Is(err, apiErr) {
|
||||
s.sendError(errInvalidTicket, c)
|
||||
} else {
|
||||
s.sendError(errInternalError, c)
|
||||
}
|
||||
log.Errorf("%s: (clientIP: %s, ticketHash: %s): %v", funcName, c.ClientIP(), hash, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure a signature is provided.
|
||||
signature := c.GetHeader("VSP-Client-Signature")
|
||||
if signature == "" {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
s.sendErrorWithMsg("no VSP-Client-Signature header", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request signature to ensure ticket ownership.
|
||||
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes), s.db, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Bad signature (clientIP=%s, ticketHash=%s): %v", funcName, err)
|
||||
s.sendError(errBadSignature, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Add ticket information to context so downstream handlers don't need
|
||||
// to access the db for it.
|
||||
c.Set(ticketKey, ticket)
|
||||
c.Set(knownTicketKey, ticketFound)
|
||||
c.Set(commitmentAddressKey, commitmentAddress)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
// payFee is the handler for "POST /api/v3/payfee".
|
||||
func payFee(c *gin.Context) {
|
||||
func (s *Server) payFee(c *gin.Context) {
|
||||
const funcName = "payFee"
|
||||
|
||||
// Get values which have been added to context by middleware.
|
||||
@ -30,26 +30,26 @@ func payFee(c *gin.Context) {
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
reqBytes := c.MustGet(requestBytesKey).([]byte)
|
||||
|
||||
if cfg.VspClosed {
|
||||
sendError(errVspClosed, c)
|
||||
if s.cfg.VspClosed {
|
||||
s.sendError(errVspClosed, c)
|
||||
return
|
||||
}
|
||||
|
||||
if !knownTicket {
|
||||
log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
|
||||
sendError(errUnknownTicket, c)
|
||||
s.sendError(errUnknownTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
var request payFeeRequest
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func payFee(c *gin.Context) {
|
||||
ticket.FeeTxStatus == database.FeeConfirmed {
|
||||
log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendError(errFeeAlreadyReceived, c)
|
||||
s.sendError(errFeeAlreadyReceived, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -67,21 +67,21 @@ func payFee(c *gin.Context) {
|
||||
rawTicket, err := dcrdClient.GetRawTransaction(ticket.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure this ticket is eligible to vote at some point in the future.
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticket.Hash, cfg.NetParams)
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticket.Hash, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.CanTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
if !canVote {
|
||||
log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendError(errTicketCannotVote, c)
|
||||
s.sendError(errTicketCannotVote, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -89,17 +89,17 @@ func payFee(c *gin.Context) {
|
||||
if ticket.FeeExpired() {
|
||||
log.Warnf("%s: Expired payfee request (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendError(errFeeExpired, c)
|
||||
s.sendError(errFeeExpired, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate VotingKey.
|
||||
votingKey := request.VotingKey
|
||||
votingWIF, err := dcrutil.DecodeWIF(votingKey, cfg.NetParams.PrivateKeyID)
|
||||
votingWIF, err := dcrutil.DecodeWIF(votingKey, s.cfg.NetParams.PrivateKeyID)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Failed to decode WIF (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendError(errInvalidPrivKey, c)
|
||||
s.sendError(errInvalidPrivKey, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ func payFee(c *gin.Context) {
|
||||
// the ticket should still be registered.
|
||||
|
||||
validVoteChoices := true
|
||||
err = validConsensusVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), request.VoteChoices)
|
||||
err = validConsensusVoteChoices(s.cfg.NetParams, currentVoteVersion(s.cfg.NetParams), request.VoteChoices)
|
||||
if err != nil {
|
||||
validVoteChoices = false
|
||||
log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
|
||||
@ -135,24 +135,24 @@ func payFee(c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Warnf("%s: Failed to decode fee tx hex (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendError(errInvalidFeeTx, c)
|
||||
s.sendError(errInvalidFeeTx, c)
|
||||
return
|
||||
}
|
||||
|
||||
err = blockchain.CheckTransactionSanity(feeTx, cfg.NetParams)
|
||||
err = blockchain.CheckTransactionSanity(feeTx, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Fee tx failed sanity check (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendError(errInvalidFeeTx, c)
|
||||
s.sendError(errInvalidFeeTx, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode fee address to get its payment script details.
|
||||
feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, cfg.NetParams)
|
||||
feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to decode fee address (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ func payFee(c *gin.Context) {
|
||||
if feePaid == 0 {
|
||||
log.Warnf("%s: Fee tx did not include expected payment (ticketHash=%s, feeAddress=%s, clientIP=%s)",
|
||||
funcName, ticket.Hash, ticket.FeeAddress, c.ClientIP())
|
||||
sendErrorWithMsg(
|
||||
s.sendErrorWithMsg(
|
||||
fmt.Sprintf("feetx did not include any payments for fee address %s", ticket.FeeAddress),
|
||||
errInvalidFeeTx, c)
|
||||
return
|
||||
@ -183,17 +183,17 @@ func payFee(c *gin.Context) {
|
||||
if feePaid < minFee {
|
||||
log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
|
||||
funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
|
||||
sendError(errFeeTooSmall, c)
|
||||
s.sendError(errFeeTooSmall, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the provided voting WIF to get its voting rights script.
|
||||
pkHash := stdaddr.Hash160(votingWIF.PubKey())
|
||||
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, cfg.NetParams)
|
||||
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: Failed to get voting address from WIF (ticketHash=%s, clientIP=%s): %v",
|
||||
funcName, ticket.Hash, c.ClientIP(), err)
|
||||
sendError(errInvalidPrivKey, c)
|
||||
s.sendError(errInvalidPrivKey, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ func payFee(c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -216,7 +216,7 @@ func payFee(c *gin.Context) {
|
||||
if actualScriptVer != wantScriptVer || !bytes.Equal(actualScript, wantScript) {
|
||||
log.Warnf("%s: Voting address does not match provided private key: (ticketHash=%s)",
|
||||
funcName, ticket.Hash)
|
||||
sendErrorWithMsg("voting address does not match provided private key",
|
||||
s.sendErrorWithMsg("voting address does not match provided private key",
|
||||
errInvalidPrivKey, c)
|
||||
return
|
||||
}
|
||||
@ -242,11 +242,11 @@ func payFee(c *gin.Context) {
|
||||
ticket.TreasuryPolicy = request.TreasuryPolicy
|
||||
}
|
||||
|
||||
err = db.UpdateTicket(ticket)
|
||||
err = s.db.UpdateTicket(ticket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -263,12 +263,12 @@ func payFee(c *gin.Context) {
|
||||
|
||||
if strings.Contains(err.Error(),
|
||||
"references outputs of unknown or fully-spent transaction") {
|
||||
sendError(errCannotBroadcastFeeUnknownOutputs, c)
|
||||
s.sendError(errCannotBroadcastFeeUnknownOutputs, c)
|
||||
} else {
|
||||
sendError(errCannotBroadcastFee, c)
|
||||
s.sendError(errCannotBroadcastFee, c)
|
||||
}
|
||||
|
||||
err = db.UpdateTicket(ticket)
|
||||
err = s.db.UpdateTicket(ticket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx error (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
@ -279,11 +279,11 @@ func payFee(c *gin.Context) {
|
||||
|
||||
ticket.FeeTxStatus = database.FeeBroadcast
|
||||
|
||||
err = db.UpdateTicket(ticket)
|
||||
err = s.db.UpdateTicket(ticket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -292,13 +292,13 @@ func payFee(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Send success response to client.
|
||||
resp, respSig := sendJSONResponse(payFeeResponse{
|
||||
resp, respSig := s.sendJSONResponse(payFeeResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: reqBytes,
|
||||
}, c)
|
||||
|
||||
// Store a record of the vote choice change.
|
||||
err = db.SaveVoteChange(
|
||||
err = s.db.SaveVoteChange(
|
||||
ticket.Hash,
|
||||
database.VoteChangeRecord{
|
||||
Request: string(reqBytes),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 The Decred developers
|
||||
// Copyright (c) 2021-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -26,7 +26,7 @@ type Node interface {
|
||||
}
|
||||
|
||||
// setAltSignAddr is the handler for "POST /api/v3/setaltsignaddr".
|
||||
func setAltSignAddr(c *gin.Context) {
|
||||
func (s *Server) setAltSignAddr(c *gin.Context) {
|
||||
|
||||
const funcName = "setAltSignAddr"
|
||||
|
||||
@ -35,49 +35,49 @@ func setAltSignAddr(c *gin.Context) {
|
||||
dcrdErr := c.MustGet(dcrdErrorKey)
|
||||
if dcrdErr != nil {
|
||||
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
reqBytes := c.MustGet(requestBytesKey).([]byte)
|
||||
|
||||
if cfg.VspClosed {
|
||||
sendError(errVspClosed, c)
|
||||
if s.cfg.VspClosed {
|
||||
s.sendError(errVspClosed, c)
|
||||
return
|
||||
}
|
||||
|
||||
var request setAltSignAddrRequest
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
altSignAddr, ticketHash := request.AltSignAddress, request.TicketHash
|
||||
|
||||
currentData, err := db.AltSignAddrData(ticketHash)
|
||||
currentData, err := s.db.AltSignAddrData(ticketHash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
if currentData != nil {
|
||||
msg := "alternate sign address data already exists"
|
||||
log.Warnf("%s: %s (ticketHash=%s)", funcName, msg, ticketHash)
|
||||
sendErrorWithMsg(msg, errBadRequest, c)
|
||||
s.sendErrorWithMsg(msg, errBadRequest, c)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// Fail fast if the pubkey doesn't decode properly.
|
||||
addr, err := stdaddr.DecodeAddressV0(altSignAddr, cfg.NetParams)
|
||||
addr, err := stdaddr.DecodeAddressV0(altSignAddr, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
if _, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok {
|
||||
log.Warnf("%s: Alt sign address is unexpected type (clientIP=%s, type=%T)", funcName, c.ClientIP(), addr)
|
||||
sendErrorWithMsg("wrong type for alternate signing address", errBadRequest, c)
|
||||
s.sendErrorWithMsg("wrong type for alternate signing address", errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -85,26 +85,26 @@ func setAltSignAddr(c *gin.Context) {
|
||||
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure this ticket is eligible to vote at some point in the future.
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticketHash, cfg.NetParams)
|
||||
canVote, err := dcrdClient.CanTicketVote(rawTicket, ticketHash, s.cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("%s: dcrd.CanTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
if !canVote {
|
||||
log.Warnf("%s: unvotable ticket (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticketHash)
|
||||
sendError(errTicketCannotVote, c)
|
||||
s.sendError(errTicketCannotVote, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Send success response to client.
|
||||
resp, respSig := sendJSONResponse(setAltSignAddrResponse{
|
||||
resp, respSig := s.sendJSONResponse(setAltSignAddrResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: reqBytes,
|
||||
}, c)
|
||||
@ -117,7 +117,7 @@ func setAltSignAddr(c *gin.Context) {
|
||||
RespSig: respSig,
|
||||
}
|
||||
|
||||
err = db.InsertAltSignAddr(ticketHash, data)
|
||||
err = s.db.InsertAltSignAddr(ticketHash, data)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.InsertAltSignAddr error (ticketHash=%s): %v",
|
||||
funcName, ticketHash, err)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -40,6 +40,7 @@ var (
|
||||
seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
feeXPub = "feexpub"
|
||||
maxVoteChangeRecords = 3
|
||||
api *Server
|
||||
)
|
||||
|
||||
// randBytes returns a byte slice of size n filled with random bytes.
|
||||
@ -58,8 +59,10 @@ func TestMain(m *testing.M) {
|
||||
log.SetLevel(slog.LevelTrace)
|
||||
|
||||
// Set up some global params.
|
||||
cfg.NetParams = chaincfg.MainNetParams()
|
||||
_, signPrivKey, _ = ed25519.GenerateKey(seededRand)
|
||||
cfg := Config{
|
||||
NetParams: chaincfg.MainNetParams(),
|
||||
}
|
||||
_, signPrivKey, _ := ed25519.GenerateKey(seededRand)
|
||||
|
||||
// Create a database to use.
|
||||
// Ensure we are starting with a clean environment.
|
||||
@ -74,11 +77,17 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error creating test database: %w", err))
|
||||
}
|
||||
db, err = database.Open(ctx, &wg, testDb, time.Hour, maxVoteChangeRecords)
|
||||
db, err := database.Open(ctx, &wg, testDb, time.Hour, maxVoteChangeRecords)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error opening test database: %w", err))
|
||||
}
|
||||
|
||||
api = &Server{
|
||||
cfg: cfg,
|
||||
signPrivKey: signPrivKey,
|
||||
db: db,
|
||||
}
|
||||
|
||||
// Run tests.
|
||||
exitCode := m.Run()
|
||||
|
||||
@ -202,12 +211,12 @@ func TestSetAltSignAddress(t *testing.T) {
|
||||
Resp: string(randBytes(1000)),
|
||||
RespSig: randString(96, sigCharset),
|
||||
}
|
||||
if err := db.InsertAltSignAddr(ticketHash, data); err != nil {
|
||||
if err := api.db.InsertAltSignAddr(ticketHash, data); err != nil {
|
||||
t.Fatalf("%q: unable to insert alt sign addr: %v", test.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg.VspClosed = test.vspClosed
|
||||
api.cfg.VspClosed = test.vspClosed
|
||||
|
||||
tNode := &testNode{
|
||||
canTicketVote: !test.canTicketNotVote,
|
||||
@ -227,7 +236,7 @@ func TestSetAltSignAddress(t *testing.T) {
|
||||
c.Set(dcrdKey, tNode)
|
||||
c.Set(dcrdErrorKey, dcrdErr)
|
||||
c.Set(requestBytesKey, b[test.deformReq:])
|
||||
setAltSignAddr(c)
|
||||
api.setAltSignAddr(c)
|
||||
}
|
||||
|
||||
r.POST("/", handle)
|
||||
@ -245,7 +254,7 @@ func TestSetAltSignAddress(t *testing.T) {
|
||||
t.Errorf("%q: expected status %d, got %d", test.name, test.wantCode, w.Code)
|
||||
}
|
||||
|
||||
altsig, err := db.AltSignAddrData(ticketHash)
|
||||
altsig, err := api.db.AltSignAddrData(ticketHash)
|
||||
if err != nil {
|
||||
t.Fatalf("%q: unable to get alt sign addr data: %v", test.name, err)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// setVoteChoices is the handler for "POST /api/v3/setvotechoices".
|
||||
func setVoteChoices(c *gin.Context) {
|
||||
func (s *Server) setVoteChoices(c *gin.Context) {
|
||||
const funcName = "setVoteChoices"
|
||||
|
||||
// Get values which have been added to context by middleware.
|
||||
@ -28,20 +28,20 @@ func 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 {
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
if !knownTicket {
|
||||
log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
|
||||
sendError(errUnknownTicket, c)
|
||||
s.sendError(errUnknownTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
if ticket.FeeTxStatus == database.NoFee {
|
||||
log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendError(errFeeNotReceived, c)
|
||||
s.sendError(errFeeNotReceived, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ func setVoteChoices(c *gin.Context) {
|
||||
if ticket.Outcome != "" {
|
||||
log.Warnf("%s: Ticket not eligible to vote (clientIP=%s, ticketHash=%s)",
|
||||
funcName, c.ClientIP(), ticket.Hash)
|
||||
sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome),
|
||||
s.sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome),
|
||||
errTicketCannotVote, c)
|
||||
return
|
||||
}
|
||||
@ -57,17 +57,17 @@ func setVoteChoices(c *gin.Context) {
|
||||
var request setVoteChoicesRequest
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), 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 := db.GetVoteChanges(ticket.Hash)
|
||||
previousChanges, err := s.db.GetVoteChanges(ticket.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func setVoteChoices(c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,18 +87,18 @@ func setVoteChoices(c *gin.Context) {
|
||||
log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+
|
||||
"than %d (ticketHash=%s)",
|
||||
funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash)
|
||||
sendError(errInvalidTimestamp, c)
|
||||
s.sendError(errInvalidTimestamp, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate vote choices (consensus, tspend policy and treasury policy).
|
||||
|
||||
err = validConsensusVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), request.VoteChoices)
|
||||
err = validConsensusVoteChoices(s.cfg.NetParams, currentVoteVersion(s.cfg.NetParams), request.VoteChoices)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -106,14 +106,14 @@ func setVoteChoices(c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
}
|
||||
|
||||
err = validTSpendPolicy(request.TSpendPolicy)
|
||||
if err != nil {
|
||||
log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
|
||||
funcName, c.ClientIP(), ticket.Hash, err)
|
||||
sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
|
||||
}
|
||||
|
||||
// Update voting preferences in the database before updating the wallets. DB
|
||||
@ -131,11 +131,11 @@ func setVoteChoices(c *gin.Context) {
|
||||
ticket.TreasuryPolicy[newTreasuryKey] = newChoice
|
||||
}
|
||||
|
||||
err = db.UpdateTicket(ticket)
|
||||
err = s.db.UpdateTicket(ticket)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to set vote choices (ticketHash=%s): %v",
|
||||
log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v",
|
||||
funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -179,13 +179,13 @@ func setVoteChoices(c *gin.Context) {
|
||||
log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash)
|
||||
|
||||
// Send success response to client.
|
||||
resp, respSig := sendJSONResponse(setVoteChoicesResponse{
|
||||
resp, respSig := s.sendJSONResponse(setVoteChoicesResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: reqBytes,
|
||||
}, c)
|
||||
|
||||
// Store a record of the vote choice change.
|
||||
err = db.SaveVoteChange(
|
||||
err = s.db.SaveVoteChange(
|
||||
ticket.Hash,
|
||||
database.VoteChangeRecord{
|
||||
Request: string(reqBytes),
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// ticketStatus is the handler for "POST /api/v3/ticketstatus".
|
||||
func ticketStatus(c *gin.Context) {
|
||||
func (s *Server) ticketStatus(c *gin.Context) {
|
||||
const funcName = "ticketStatus"
|
||||
|
||||
// Get values which have been added to context by middleware.
|
||||
@ -23,22 +23,22 @@ func ticketStatus(c *gin.Context) {
|
||||
|
||||
if !knownTicket {
|
||||
log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
|
||||
sendError(errUnknownTicket, c)
|
||||
s.sendError(errUnknownTicket, c)
|
||||
return
|
||||
}
|
||||
|
||||
var request ticketStatusRequest
|
||||
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
|
||||
sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// Get altSignAddress from database
|
||||
altSignAddrData, err := db.AltSignAddrData(ticket.Hash)
|
||||
altSignAddrData, err := s.db.AltSignAddrData(ticket.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ func ticketStatus(c *gin.Context) {
|
||||
altSignAddr = altSignAddrData.AltSignAddr
|
||||
}
|
||||
|
||||
sendJSONResponse(ticketStatusResponse{
|
||||
s.sendJSONResponse(ticketStatusResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: reqBytes,
|
||||
TicketConfirmed: ticket.Confirmed,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -12,16 +12,16 @@ import (
|
||||
)
|
||||
|
||||
// vspInfo is the handler for "GET /api/v3/vspinfo".
|
||||
func vspInfo(c *gin.Context) {
|
||||
func (s *Server) vspInfo(c *gin.Context) {
|
||||
cachedStats := getCache()
|
||||
sendJSONResponse(vspInfoResponse{
|
||||
s.sendJSONResponse(vspInfoResponse{
|
||||
APIVersions: []int64{3},
|
||||
Timestamp: time.Now().Unix(),
|
||||
PubKey: signPubKey,
|
||||
FeePercentage: cfg.VSPFee,
|
||||
Network: cfg.NetParams.Name,
|
||||
VspClosed: cfg.VspClosed,
|
||||
VspClosedMsg: cfg.VspClosedMsg,
|
||||
PubKey: s.signPubKey,
|
||||
FeePercentage: s.cfg.VSPFee,
|
||||
Network: s.cfg.NetParams.Name,
|
||||
VspClosed: s.cfg.VspClosed,
|
||||
VspClosedMsg: s.cfg.VspClosedMsg,
|
||||
VspdVersion: version.String(),
|
||||
Voting: cachedStats.Voting,
|
||||
Voted: cachedStats.Voted,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Copyright (c) 2020-2022 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -63,28 +63,32 @@ const (
|
||||
commitmentAddressKey = "CommitmentAddress"
|
||||
)
|
||||
|
||||
var cfg Config
|
||||
var db *database.VspDatabase
|
||||
var addrGen *addressGenerator
|
||||
var signPrivKey ed25519.PrivateKey
|
||||
var signPubKey ed25519.PublicKey
|
||||
type Server struct {
|
||||
cfg Config
|
||||
db *database.VspDatabase
|
||||
addrGen *addressGenerator
|
||||
signPrivKey ed25519.PrivateKey
|
||||
signPubKey ed25519.PublicKey
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGroup,
|
||||
listen string, vdb *database.VspDatabase, dcrd rpc.DcrdConnect, wallets rpc.WalletConnect, config Config) error {
|
||||
|
||||
cfg = config
|
||||
db = vdb
|
||||
s := &Server{
|
||||
cfg: config,
|
||||
db: vdb,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Get keys for signing API responses from the database.
|
||||
signPrivKey, signPubKey, err = vdb.KeyPair()
|
||||
s.signPrivKey, s.signPubKey, err = vdb.KeyPair()
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Keypair error: %w", err)
|
||||
}
|
||||
|
||||
// Populate cached VSP stats before starting webserver.
|
||||
initCache()
|
||||
initCache(base64.StdEncoding.EncodeToString(s.signPubKey))
|
||||
err = updateCache(ctx, vdb, dcrd, config.NetParams, wallets)
|
||||
if err != nil {
|
||||
log.Errorf("Could not initialize VSP stats cache: %v", err)
|
||||
@ -100,7 +104,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.GetFeeXPub error: %w", err)
|
||||
}
|
||||
addrGen, err = newAddressGenerator(feeXPub, config.NetParams, idx)
|
||||
s.addrGen, err = newAddressGenerator(feeXPub, config.NetParams, idx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize fee address generator: %w", err)
|
||||
}
|
||||
@ -120,7 +124,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
|
||||
log.Infof("Listening on %s", listen)
|
||||
|
||||
srv := http.Server{
|
||||
Handler: router(cfg.Debug, cookieSecret, dcrd, wallets),
|
||||
Handler: s.router(cookieSecret, dcrd, wallets),
|
||||
ReadTimeout: 5 * time.Second, // slow requests should not hold connections opened
|
||||
WriteTimeout: 60 * time.Second, // hung responses must die
|
||||
}
|
||||
@ -157,7 +161,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
|
||||
|
||||
// Use a ticker to update cached VSP stats.
|
||||
var refresh time.Duration
|
||||
if cfg.Debug {
|
||||
if s.cfg.Debug {
|
||||
refresh = 1 * time.Second
|
||||
} else {
|
||||
refresh = 1 * time.Minute
|
||||
@ -183,10 +187,10 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
|
||||
return nil
|
||||
}
|
||||
|
||||
func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.WalletConnect) *gin.Engine {
|
||||
func (s *Server) 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 !debugMode {
|
||||
if !s.cfg.Debug {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
@ -194,9 +198,9 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r
|
||||
|
||||
// Add custom functions for use in templates.
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"txURL": txURL(cfg.BlockExplorerURL),
|
||||
"addressURL": addressURL(cfg.BlockExplorerURL),
|
||||
"blockURL": blockURL(cfg.BlockExplorerURL),
|
||||
"txURL": txURL(s.cfg.BlockExplorerURL),
|
||||
"addressURL": addressURL(s.cfg.BlockExplorerURL),
|
||||
"blockURL": blockURL(s.cfg.BlockExplorerURL),
|
||||
"dateTime": dateTime,
|
||||
"stripWss": stripWss,
|
||||
"indentJSON": indentJSON,
|
||||
@ -212,7 +216,7 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r
|
||||
// sending no response at all.
|
||||
router.Use(Recovery())
|
||||
|
||||
if debugMode {
|
||||
if s.cfg.Debug {
|
||||
// Logger middleware outputs very detailed logging of webserver requests
|
||||
// to the terminal. Does not get logged to file.
|
||||
router.Use(gin.Logger())
|
||||
@ -227,37 +231,37 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r
|
||||
// API routes.
|
||||
|
||||
api := router.Group("/api/v3")
|
||||
api.GET("/vspinfo", vspInfo)
|
||||
api.POST("/setaltsignaddr", withDcrdClient(dcrd), broadcastTicket(), vspAuth(), setAltSignAddr)
|
||||
api.POST("/feeaddress", withDcrdClient(dcrd), broadcastTicket(), vspAuth(), feeAddress)
|
||||
api.POST("/ticketstatus", withDcrdClient(dcrd), vspAuth(), ticketStatus)
|
||||
api.POST("/payfee", withDcrdClient(dcrd), vspAuth(), payFee)
|
||||
api.POST("/setvotechoices", withDcrdClient(dcrd), withWalletClients(wallets), vspAuth(), setVoteChoices)
|
||||
api.GET("/vspinfo", s.vspInfo)
|
||||
api.POST("/setaltsignaddr", withDcrdClient(dcrd, s.cfg), s.broadcastTicket, s.vspAuth, s.setAltSignAddr)
|
||||
api.POST("/feeaddress", withDcrdClient(dcrd, s.cfg), s.broadcastTicket, s.vspAuth, s.feeAddress)
|
||||
api.POST("/ticketstatus", withDcrdClient(dcrd, s.cfg), s.vspAuth, s.ticketStatus)
|
||||
api.POST("/payfee", withDcrdClient(dcrd, s.cfg), s.vspAuth, s.payFee)
|
||||
api.POST("/setvotechoices", withDcrdClient(dcrd, s.cfg), withWalletClients(wallets, s.cfg), s.vspAuth, s.setVoteChoices)
|
||||
|
||||
// Website routes.
|
||||
|
||||
router.GET("", homepage)
|
||||
router.GET("", s.homepage)
|
||||
|
||||
login := router.Group("/admin").Use(
|
||||
withSession(cookieStore),
|
||||
)
|
||||
login.POST("", adminLogin)
|
||||
login.POST("", s.adminLogin)
|
||||
|
||||
admin := router.Group("/admin").Use(
|
||||
withWalletClients(wallets), withSession(cookieStore), requireAdmin(),
|
||||
withWalletClients(wallets, s.cfg), withSession(cookieStore), s.requireAdmin,
|
||||
)
|
||||
admin.GET("", withDcrdClient(dcrd), adminPage)
|
||||
admin.POST("/ticket", withDcrdClient(dcrd), ticketSearch)
|
||||
admin.GET("/backup", downloadDatabaseBackup)
|
||||
admin.POST("/logout", adminLogout)
|
||||
admin.GET("", withDcrdClient(dcrd, s.cfg), s.adminPage)
|
||||
admin.POST("/ticket", withDcrdClient(dcrd, s.cfg), s.ticketSearch)
|
||||
admin.GET("/backup", s.downloadDatabaseBackup)
|
||||
admin.POST("/logout", s.adminLogout)
|
||||
|
||||
// Require Basic HTTP Auth on /admin/status endpoint.
|
||||
basic := router.Group("/admin").Use(
|
||||
withDcrdClient(dcrd), withWalletClients(wallets), gin.BasicAuth(gin.Accounts{
|
||||
"admin": cfg.AdminPass,
|
||||
withDcrdClient(dcrd, s.cfg), withWalletClients(wallets, s.cfg), gin.BasicAuth(gin.Accounts{
|
||||
"admin": s.cfg.AdminPass,
|
||||
}),
|
||||
)
|
||||
basic.GET("/status", statusJSON)
|
||||
basic.GET("/status", s.statusJSON)
|
||||
|
||||
return router
|
||||
}
|
||||
@ -265,15 +269,15 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r
|
||||
// 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 sendJSONResponse(resp interface{}, c *gin.Context) (string, string) {
|
||||
func (s *Server) sendJSONResponse(resp interface{}, c *gin.Context) (string, string) {
|
||||
dec, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
log.Errorf("JSON marshal error: %v", err)
|
||||
sendError(errInternalError, c)
|
||||
s.sendError(errInternalError, c)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
sig := ed25519.Sign(signPrivKey, dec)
|
||||
sig := ed25519.Sign(s.signPrivKey, dec)
|
||||
sigStr := base64.StdEncoding.EncodeToString(sig)
|
||||
c.Writer.Header().Set("VSP-Server-Signature", sigStr)
|
||||
|
||||
@ -284,14 +288,14 @@ func sendJSONResponse(resp interface{}, c *gin.Context) (string, string) {
|
||||
|
||||
// sendError sends an error response to the client using the default error
|
||||
// message.
|
||||
func sendError(e apiError, c *gin.Context) {
|
||||
func (s *Server) sendError(e apiError, c *gin.Context) {
|
||||
msg := e.Error()
|
||||
sendErrorWithMsg(msg, e, c)
|
||||
s.sendErrorWithMsg(msg, e, c)
|
||||
}
|
||||
|
||||
// sendErrorWithMsg sends an error response to the client using the provided
|
||||
// error message.
|
||||
func sendErrorWithMsg(msg string, e apiError, c *gin.Context) {
|
||||
func (s *Server) sendErrorWithMsg(msg string, e apiError, c *gin.Context) {
|
||||
status := e.httpStatus()
|
||||
|
||||
resp := gin.H{
|
||||
@ -304,7 +308,7 @@ func sendErrorWithMsg(msg string, e apiError, c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Warnf("Sending error response without signature: %v", err)
|
||||
} else {
|
||||
sig := ed25519.Sign(signPrivKey, dec)
|
||||
sig := ed25519.Sign(s.signPrivKey, dec)
|
||||
c.Writer.Header().Set("VSP-Server-Signature", base64.StdEncoding.EncodeToString(sig))
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user