Add types module for API requests/responses/errors. (#356)

* Remove helper func getCommitmentAddress.

This function is only used in one place so removing it does not introduce any duplication. Removing the func also removes the need for errors.Is comparison, which will be very useful in upcoming changes.

* Rename apiError to ErrorCode.

* Don't use inline type for API errors.

* Export webapi errors.

* Export webapi request/responses.

* Add types module for API requests/responses/errors
This commit is contained in:
Jamie Holdstock 2022-11-19 04:06:47 +08:00 committed by GitHub
parent a5998264b3
commit ed1fac1a2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 468 additions and 384 deletions

5
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/decred/dcrd/txscript/v4 v4.0.0
github.com/decred/dcrd/wire v1.5.0
github.com/decred/slog v1.2.0
github.com/decred/vspd/types v0.0.0-00010101000000-000000000000
github.com/dustin/go-humanize v1.0.0
github.com/gin-gonic/gin v1.8.1
github.com/gorilla/sessions v1.2.1
@ -61,3 +62,7 @@ require (
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace (
github.com/decred/vspd/types => ./types
)

View File

@ -1,18 +1,39 @@
#!/bin/bash
#
# 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.
#
# usage:
# ./run_tests.sh
set -ex
set -e
go version
# run tests
env GORACE="halt_on_error=1" go test -race ./...
# run tests on all modules
ROOTPATH=$(go list -m)
ROOTPATHPATTERN=$(echo $ROOTPATH | sed 's/\\/\\\\/g' | sed 's/\//\\\//g')
MODPATHS=$(go list -m all | grep "^$ROOTPATHPATTERN" | cut -d' ' -f1)
for module in $MODPATHS; do
echo "==> ${module}"
env GORACE="halt_on_error=1" go test -race ${module}/...
# run linter
golangci-lint run
# check linters
MODNAME=$(echo $module | sed -E -e "s/^$ROOTPATHPATTERN//" \
-e 's,^/,,' -e 's,/v[0-9]+$,,')
if [ -z "$MODNAME" ]; then
MODNAME=.
fi
# run commands in the module directory as a subshell
(
cd $MODNAME
# run linter
golangci-lint run
)
done
echo "------------------------------------------"
echo "Tests completed successfully!"

120
types/errors.go Normal file
View File

@ -0,0 +1,120 @@
// 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.
package types
import "net/http"
// ErrorCode is an integer which represents a kind of error which may be
// encountered by vspd.
type ErrorCode int64
const (
ErrBadRequest ErrorCode = iota
ErrInternalError
ErrVspClosed
ErrFeeAlreadyReceived
ErrInvalidFeeTx
ErrFeeTooSmall
ErrUnknownTicket
ErrTicketCannotVote
ErrFeeExpired
ErrInvalidVoteChoices
ErrBadSignature
ErrInvalidPrivKey
ErrFeeNotReceived
ErrInvalidTicket
ErrCannotBroadcastTicket
ErrCannotBroadcastFee
ErrCannotBroadcastFeeUnknownOutputs
ErrInvalidTimestamp
)
// HTTPStatus returns a corresponding HTTP status code for a given error code.
func (e ErrorCode) HTTPStatus() int {
switch e {
case ErrBadRequest:
return http.StatusBadRequest
case ErrInternalError:
return http.StatusInternalServerError
case ErrVspClosed:
return http.StatusBadRequest
case ErrFeeAlreadyReceived:
return http.StatusBadRequest
case ErrInvalidFeeTx:
return http.StatusBadRequest
case ErrFeeTooSmall:
return http.StatusBadRequest
case ErrUnknownTicket:
return http.StatusBadRequest
case ErrTicketCannotVote:
return http.StatusBadRequest
case ErrFeeExpired:
return http.StatusBadRequest
case ErrInvalidVoteChoices:
return http.StatusBadRequest
case ErrBadSignature:
return http.StatusBadRequest
case ErrInvalidPrivKey:
return http.StatusBadRequest
case ErrFeeNotReceived:
return http.StatusBadRequest
case ErrInvalidTicket:
return http.StatusBadRequest
case ErrCannotBroadcastTicket:
return http.StatusInternalServerError
case ErrCannotBroadcastFee:
return http.StatusInternalServerError
case ErrCannotBroadcastFeeUnknownOutputs:
return http.StatusPreconditionRequired
case ErrInvalidTimestamp:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
// DefaultMessage returns a descriptive error string for a given error code.
func (e ErrorCode) DefaultMessage() string {
switch e {
case ErrBadRequest:
return "bad request"
case ErrInternalError:
return "internal error"
case ErrVspClosed:
return "vsp is closed"
case ErrFeeAlreadyReceived:
return "fee tx already received for ticket"
case ErrInvalidFeeTx:
return "invalid fee tx"
case ErrFeeTooSmall:
return "fee too small"
case ErrUnknownTicket:
return "unknown ticket"
case ErrTicketCannotVote:
return "ticket not eligible to vote"
case ErrFeeExpired:
return "fee has expired"
case ErrInvalidVoteChoices:
return "invalid vote choices"
case ErrBadSignature:
return "bad request signature"
case ErrInvalidPrivKey:
return "invalid private key"
case ErrFeeNotReceived:
return "no fee tx received for ticket"
case ErrInvalidTicket:
return "not a valid ticket tx"
case ErrCannotBroadcastTicket:
return "ticket transaction could not be broadcast"
case ErrCannotBroadcastFee:
return "fee transaction could not be broadcast"
case ErrCannotBroadcastFeeUnknownOutputs:
return "fee transaction could not be broadcast due to unknown outputs"
case ErrInvalidTimestamp:
return "old or reused timestamp"
default:
return "unknown error"
}
}

85
types/errors_test.go Normal file
View File

@ -0,0 +1,85 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package types
import (
"net/http"
"testing"
)
// TestErrorDefaultMessages ensures each ErrorKind can be mapped to a default
// descriptive error message.
func TestErrorDefaultMessages(t *testing.T) {
tests := []struct {
in ErrorCode
wantMsg string
}{
{ErrBadRequest, "bad request"},
{ErrInternalError, "internal error"},
{ErrVspClosed, "vsp is closed"},
{ErrFeeAlreadyReceived, "fee tx already received for ticket"},
{ErrInvalidFeeTx, "invalid fee tx"},
{ErrFeeTooSmall, "fee too small"},
{ErrUnknownTicket, "unknown ticket"},
{ErrTicketCannotVote, "ticket not eligible to vote"},
{ErrFeeExpired, "fee has expired"},
{ErrInvalidVoteChoices, "invalid vote choices"},
{ErrBadSignature, "bad request signature"},
{ErrInvalidPrivKey, "invalid private key"},
{ErrFeeNotReceived, "no fee tx received for ticket"},
{ErrInvalidTicket, "not a valid ticket tx"},
{ErrCannotBroadcastTicket, "ticket transaction could not be broadcast"},
{ErrCannotBroadcastFee, "fee transaction could not be broadcast"},
{ErrCannotBroadcastFeeUnknownOutputs, "fee transaction could not be broadcast due to unknown outputs"},
{ErrInvalidTimestamp, "old or reused timestamp"},
{ErrorCode(9999), "unknown error"},
}
for _, test := range tests {
actualMsg := test.in.DefaultMessage()
if actualMsg != test.wantMsg {
t.Errorf("wrong default message for ErrorKind(%d). expected: %q actual: %q ",
test.in, test.wantMsg, actualMsg)
continue
}
}
}
// TestErrorHTTPStatus ensures each ErrorCode can be mapped to a corresponding HTTP status code.
func TestErrorHTTPStatus(t *testing.T) {
tests := []struct {
in ErrorCode
wantStatus int
}{
{ErrBadRequest, http.StatusBadRequest},
{ErrInternalError, http.StatusInternalServerError},
{ErrVspClosed, http.StatusBadRequest},
{ErrFeeAlreadyReceived, http.StatusBadRequest},
{ErrInvalidFeeTx, http.StatusBadRequest},
{ErrFeeTooSmall, http.StatusBadRequest},
{ErrUnknownTicket, http.StatusBadRequest},
{ErrTicketCannotVote, http.StatusBadRequest},
{ErrFeeExpired, http.StatusBadRequest},
{ErrInvalidVoteChoices, http.StatusBadRequest},
{ErrBadSignature, http.StatusBadRequest},
{ErrInvalidPrivKey, http.StatusBadRequest},
{ErrFeeNotReceived, http.StatusBadRequest},
{ErrInvalidTicket, http.StatusBadRequest},
{ErrCannotBroadcastTicket, http.StatusInternalServerError},
{ErrCannotBroadcastFee, http.StatusInternalServerError},
{ErrCannotBroadcastFeeUnknownOutputs, http.StatusPreconditionRequired},
{ErrInvalidTimestamp, http.StatusBadRequest},
{ErrorCode(9999), http.StatusInternalServerError},
}
for _, test := range tests {
result := test.in.HTTPStatus()
if result != test.wantStatus {
t.Errorf("wrong HTTP status for ErrorKind(%d). expected: %d actual: %d ",
test.in, test.wantStatus, result)
continue
}
}
}

3
types/go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/decred/vspd/types
go 1.19

View File

@ -2,9 +2,16 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package webapi
package types
type vspInfoResponse struct {
type APIError struct {
Code int64 `json:"code"`
Message string `json:"message"`
}
func (e APIError) Error() string { return e.Message }
type VspInfoResponse struct {
APIVersions []int64 `json:"apiversions"`
Timestamp int64 `json:"timestamp"`
PubKey []byte `json:"pubkey"`
@ -22,14 +29,14 @@ type vspInfoResponse struct {
NetworkProportion float32 `json:"estimatednetworkproportion"`
}
type feeAddressRequest struct {
type FeeAddressRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"`
TicketHex string `json:"tickethex" binding:"required"`
ParentHex string `json:"parenthex" binding:"required"`
}
type feeAddressResponse struct {
type FeeAddressResponse struct {
Timestamp int64 `json:"timestamp"`
FeeAddress string `json:"feeaddress"`
FeeAmount int64 `json:"feeamount"`
@ -37,7 +44,7 @@ type feeAddressResponse struct {
Request []byte `json:"request"`
}
type payFeeRequest struct {
type PayFeeRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"`
FeeTx string `json:"feetx" binding:"required"`
@ -47,12 +54,12 @@ type payFeeRequest struct {
TreasuryPolicy map[string]string `json:"treasurypolicy" binding:"max=3"`
}
type payFeeResponse struct {
type PayFeeResponse struct {
Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"`
}
type setVoteChoicesRequest struct {
type SetVoteChoicesRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"`
VoteChoices map[string]string `json:"votechoices" binding:"required"`
@ -60,16 +67,16 @@ type setVoteChoicesRequest struct {
TreasuryPolicy map[string]string `json:"treasurypolicy" binding:"max=3"`
}
type setVoteChoicesResponse struct {
type SetVoteChoicesResponse struct {
Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"`
}
type ticketStatusRequest struct {
type TicketStatusRequest struct {
TicketHash string `json:"tickethash" binding:"required"`
}
type ticketStatusResponse struct {
type TicketStatusResponse struct {
Timestamp int64 `json:"timestamp"`
TicketConfirmed bool `json:"ticketconfirmed"`
FeeTxStatus string `json:"feetxstatus"`
@ -81,7 +88,7 @@ type ticketStatusResponse struct {
Request []byte `json:"request"`
}
type setAltSignAddrRequest struct {
type SetAltSignAddrRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"`
TicketHex string `json:"tickethex" binding:"required"`
@ -89,7 +96,7 @@ type setAltSignAddrRequest struct {
AltSignAddress string `json:"altsignaddress" binding:"required"`
}
type setAltSignAddrResponse struct {
type SetAltSignAddrResponse struct {
Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"`
}

53
types/types_test.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package types
import (
"errors"
"testing"
)
// TestAPIErrorAs ensures APIError can be unwrapped via errors.As.
func TestAPIErrorAs(t *testing.T) {
tests := []struct {
testName string
apiError error
expectedKind ErrorCode
expectedMessage string
}{{
testName: "BadRequest error",
apiError: APIError{Message: "something went wrong", Code: int64(ErrBadRequest)},
expectedKind: ErrBadRequest,
expectedMessage: "something went wrong",
},
{
testName: "Unknown error",
apiError: APIError{Message: "something went wrong again", Code: int64(999)},
expectedKind: 999,
expectedMessage: "something went wrong again",
}}
for _, test := range tests {
// Ensure APIError can be unwrapped from error.
var parsedError APIError
if !errors.As(test.apiError, &parsedError) {
t.Errorf("%s: unable to unwrap error", test.testName)
continue
}
if parsedError.Code != int64(test.expectedKind) {
t.Errorf("%s: error was wrong kind. expected: %d actual %d",
test.testName, test.expectedKind, parsedError.Code)
continue
}
if parsedError.Message != test.expectedMessage {
t.Errorf("%s: error had wrong message. expected: %q actual %q",
test.testName, test.expectedMessage, parsedError.Message)
continue
}
}
}

View File

@ -1,125 +0,0 @@
// Copyright (c) 2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package webapi
import "net/http"
// apiError is a kind of error. It has full support for errors.Is and
// errors.As.
type apiError int
const (
errBadRequest apiError = iota
errInternalError
errVspClosed
errFeeAlreadyReceived
errInvalidFeeTx
errFeeTooSmall
errUnknownTicket
errTicketCannotVote
errFeeExpired
errInvalidVoteChoices
errBadSignature
errInvalidPrivKey
errFeeNotReceived
errInvalidTicket
errCannotBroadcastTicket
errCannotBroadcastFee
errCannotBroadcastFeeUnknownOutputs
errInvalidTimestamp
)
// httpStatus maps application error codes to HTTP status codes.
func (e apiError) httpStatus() int {
switch e {
case errBadRequest:
return http.StatusBadRequest
case errInternalError:
return http.StatusInternalServerError
case errVspClosed:
return http.StatusBadRequest
case errFeeAlreadyReceived:
return http.StatusBadRequest
case errInvalidFeeTx:
return http.StatusBadRequest
case errFeeTooSmall:
return http.StatusBadRequest
case errUnknownTicket:
return http.StatusBadRequest
case errTicketCannotVote:
return http.StatusBadRequest
case errFeeExpired:
return http.StatusBadRequest
case errInvalidVoteChoices:
return http.StatusBadRequest
case errBadSignature:
return http.StatusBadRequest
case errInvalidPrivKey:
return http.StatusBadRequest
case errFeeNotReceived:
return http.StatusBadRequest
case errInvalidTicket:
return http.StatusBadRequest
case errCannotBroadcastTicket:
return http.StatusInternalServerError
case errCannotBroadcastFee:
return http.StatusInternalServerError
case errCannotBroadcastFeeUnknownOutputs:
return http.StatusPreconditionRequired
case errInvalidTimestamp:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
// String returns a descriptive error string for a given error code.
func (e apiError) String() string {
switch e {
case errBadRequest:
return "bad request"
case errInternalError:
return "internal error"
case errVspClosed:
return "vsp is closed"
case errFeeAlreadyReceived:
return "fee tx already received for ticket"
case errInvalidFeeTx:
return "invalid fee tx"
case errFeeTooSmall:
return "fee too small"
case errUnknownTicket:
return "unknown ticket"
case errTicketCannotVote:
return "ticket not eligible to vote"
case errFeeExpired:
return "fee has expired"
case errInvalidVoteChoices:
return "invalid vote choices"
case errBadSignature:
return "bad request signature"
case errInvalidPrivKey:
return "invalid private key"
case errFeeNotReceived:
return "no fee tx received for ticket"
case errInvalidTicket:
return "not a valid ticket tx"
case errCannotBroadcastTicket:
return "ticket transaction could not be broadcast"
case errCannotBroadcastFee:
return "fee transaction could not be broadcast"
case errCannotBroadcastFeeUnknownOutputs:
return "fee transaction could not be broadcast due to unknown outputs"
case errInvalidTimestamp:
return "old or reused timestamp"
default:
return "unknown error"
}
}
// Error satisfies the error interface and returns a human-readable error string.
func (e apiError) Error() string {
return e.String()
}

View File

@ -1,90 +0,0 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package webapi
import (
"errors"
"testing"
)
// TestApiErrorString tests the stringized output for the apiError type.
func TestApiErrorString(t *testing.T) {
tests := []struct {
in apiError
want string
}{
{errBadRequest, "bad request"},
{errInternalError, "internal error"},
{errVspClosed, "vsp is closed"},
{errFeeAlreadyReceived, "fee tx already received for ticket"},
{errInvalidFeeTx, "invalid fee tx"},
{errFeeTooSmall, "fee too small"},
{errUnknownTicket, "unknown ticket"},
{errTicketCannotVote, "ticket not eligible to vote"},
{errFeeExpired, "fee has expired"},
{errInvalidVoteChoices, "invalid vote choices"},
{errBadSignature, "bad request signature"},
{errInvalidPrivKey, "invalid private key"},
{errFeeNotReceived, "no fee tx received for ticket"},
{errInvalidTicket, "not a valid ticket tx"},
{errCannotBroadcastTicket, "ticket transaction could not be broadcast"},
{errCannotBroadcastFee, "fee transaction could not be broadcast"},
{errCannotBroadcastFeeUnknownOutputs, "fee transaction could not be broadcast due to unknown outputs"},
{errInvalidTimestamp, "old or reused timestamp"},
}
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestApiErrorIsAs ensures apiError can be identified via errors.Is and unwrapped via errors.As.
func TestApiErrorIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs apiError
}{{
name: "errBadRequest == errBadRequest",
err: errBadRequest,
target: errBadRequest,
wantMatch: true,
wantAs: errBadRequest,
}, {
name: "errBadRequest != errInternalError",
err: errBadRequest,
target: errInternalError,
wantMatch: false,
wantAs: errBadRequest,
}}
for _, test := range tests {
// Ensure the error matches or not depending on the expected result.
result := errors.Is(test.err, test.target)
if result != test.wantMatch {
t.Errorf("%s: incorrect error identification -- got %v, want %v",
test.name, result, test.wantMatch)
continue
}
// Ensure the underlying apiError can be unwrapped and is the
// expected type.
var err apiError
if !errors.As(test.err, &err) {
t.Errorf("%s: unable to unwrap error", test.name)
continue
}
if err != test.wantAs {
t.Errorf("%s: unexpected unwrapped error -- got %v, want %v",
test.name, err, test.wantAs)
continue
}
}
}

View File

@ -12,6 +12,7 @@ import (
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
@ -84,20 +85,20 @@ func (s *Server) feeAddress(c *gin.Context) {
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
if s.cfg.VspClosed {
s.sendError(errVspClosed, c)
s.sendError(types.ErrVspClosed, c)
return
}
var request feeAddressRequest
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -110,7 +111,7 @@ func (s *Server) feeAddress(c *gin.Context) {
ticket.FeeTxStatus == database.FeeConfirmed) {
s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeAlreadyReceived, c)
s.sendError(types.ErrFeeAlreadyReceived, c)
return
}
@ -118,7 +119,7 @@ func (s *Server) feeAddress(c *gin.Context) {
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(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -126,13 +127,13 @@ func (s *Server) feeAddress(c *gin.Context) {
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.NetParams)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticketHash)
s.sendError(errTicketCannotVote, c)
s.sendError(types.ErrTicketCannotVote, c)
return
}
@ -145,7 +146,7 @@ func (s *Server) feeAddress(c *gin.Context) {
newFee, err := s.getCurrentFee(dcrdClient)
if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
ticket.FeeExpiration = now.Add(feeAddressExpiration).Unix()
@ -155,13 +156,13 @@ func (s *Server) feeAddress(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
s.log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)",
funcName, newFee, ticket.Hash)
}
s.sendJSONResponse(feeAddressResponse{
s.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(),
Request: reqBytes,
FeeAddress: ticket.FeeAddress,
@ -178,14 +179,14 @@ func (s *Server) feeAddress(c *gin.Context) {
fee, err := s.getCurrentFee(dcrdClient)
if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
newAddress, newAddressIdx, err := s.getNewFeeAddress()
if err != nil {
s.log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -216,7 +217,7 @@ func (s *Server) feeAddress(c *gin.Context) {
err = s.db.InsertNewTicket(dbTicket)
if err != nil {
s.log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -224,7 +225,7 @@ func (s *Server) feeAddress(c *gin.Context) {
"feeAddr=%s, feeAmt=%s, ticketHash=%s)",
funcName, confirmed, newAddressIdx, newAddress, fee, ticketHash)
s.sendJSONResponse(feeAddressResponse{
s.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(),
Request: reqBytes,
FeeAddress: newAddress,

View File

@ -17,7 +17,6 @@ import (
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v3"
"github.com/decred/dcrd/wire"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
)
func currentVoteVersion(params *chaincfg.Params) uint32 {
@ -170,32 +169,6 @@ func validateTicketHash(hash string) error {
return nil
}
// getCommitmentAddress gets the commitment address of the provided ticket hash
// from the chain.
func getCommitmentAddress(hash string, dcrdClient *rpc.DcrdRPC, params *chaincfg.Params) (string, error) {
resp, err := dcrdClient.GetRawTransaction(hash)
if err != nil {
return "", fmt.Errorf("dcrd.GetRawTransaction for ticket failed: %w", err)
}
msgTx, err := decodeTransaction(resp.Hex)
if err != nil {
return "", fmt.Errorf("failed to decode ticket hex: %w", err)
}
err = isValidTicket(msgTx)
if err != nil {
return "", fmt.Errorf("invalid ticket: %w", errInvalidTicket)
}
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, params)
if err != nil {
return "", fmt.Errorf("AddrFromSStxPkScrCommitment error: %w", err)
}
return addr.String(), nil
}
// canTicketVote checks determines whether a ticket is able to vote at some
// point in the future by checking that it is currently either immature or live.
func canTicketVote(rawTx *dcrdtypes.TxRawResult, dcrdClient Node, netParams *chaincfg.Params) (bool, error) {

View File

@ -11,7 +11,9 @@ import (
"net/http"
"strings"
"github.com/decred/dcrd/blockchain/stake/v4"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/gorilla/sessions"
@ -121,7 +123,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -133,7 +135,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
}
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -142,7 +144,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendErrorWithMsg("cannot decode ticket hex", errBadRequest, c)
s.sendErrorWithMsg("cannot decode ticket hex", types.ErrBadRequest, c)
return
}
@ -150,7 +152,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), request.TicketHash, err)
s.sendError(errInvalidTicket, c)
s.sendError(types.ErrInvalidTicket, c)
return
}
@ -158,7 +160,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
if msgTx.TxHash().String() != request.TicketHash {
s.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)
s.sendErrorWithMsg("ticket hex does not match hash", types.ErrBadRequest, c)
return
}
@ -166,7 +168,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
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", errBadRequest, c)
s.sendErrorWithMsg("cannot decode parent hex", types.ErrBadRequest, c)
return
}
parentHash := parentTx.TxHash()
@ -176,7 +178,7 @@ func (s *Server) broadcastTicket(c *gin.Context) {
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -201,7 +203,7 @@ 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", errBadRequest, c)
s.sendErrorWithMsg("invalid ticket parent", types.ErrBadRequest, c)
return
}
@ -210,14 +212,14 @@ func (s *Server) broadcastTicket(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: dcrd.SendRawTransaction for parent tx failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(errCannotBroadcastTicket, c)
s.sendError(types.ErrCannotBroadcastTicket, c)
return
}
} else {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket parent failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -236,13 +238,13 @@ func (s *Server) broadcastTicket(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: dcrd.SendRawTransaction for ticket failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(errCannotBroadcastTicket, c)
s.sendError(types.ErrCannotBroadcastTicket, c)
return
}
} else {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v",
funcName, request.TicketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
}
@ -261,7 +263,7 @@ func (s *Server) vspAuth(c *gin.Context) {
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -275,7 +277,7 @@ func (s *Server) vspAuth(c *gin.Context) {
}
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
hash := request.TicketHash
@ -284,7 +286,7 @@ func (s *Server) vspAuth(c *gin.Context) {
err = validateTicketHash(hash)
if err != nil {
s.log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
s.sendErrorWithMsg("invalid ticket hash", types.ErrBadRequest, c)
return
}
@ -292,45 +294,67 @@ func (s *Server) vspAuth(c *gin.Context) {
ticket, ticketFound, err := s.db.GetTicketByHash(hash)
if err != nil {
s.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.
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(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
var commitmentAddress string
if ticketFound {
// The commitment address is already known if the ticket already exists
// in the database.
commitmentAddress = ticket.CommitmentAddress
} else {
commitmentAddress, err = getCommitmentAddress(hash, dcrdClient, s.cfg.NetParams)
if err != nil {
s.log.Errorf("%s: Failed to get commitment address (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
var apiErr *apiError
if errors.Is(err, apiErr) {
s.sendError(errInvalidTicket, c)
} else {
s.sendError(errInternalError, c)
}
// Otherwise the commitment address must be retrieved from the chain
// using dcrd.
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",
funcName, c.ClientIP(), hash, dcrdErr.(error))
s.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",
funcName, c.ClientIP(), hash, err)
s.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",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrInternalError, c)
return
}
err = isValidTicket(msgTx)
if err != nil {
s.log.Errorf("%s: Invalid ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), hash)
s.sendError(types.ErrInvalidTicket, c)
return
}
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, s.cfg.NetParams)
if err != nil {
s.log.Errorf("%s: AddrFromSStxPkScrCommitment error (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(types.ErrInternalError, c)
return
}
commitmentAddress = addr.String()
}
// 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", errBadRequest, c)
s.sendErrorWithMsg("no VSP-Client-Signature header", types.ErrBadRequest, c)
return
}
@ -339,7 +363,7 @@ func (s *Server) vspAuth(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: Couldn't validate signature (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), hash, err)
s.sendError(errBadSignature, c)
s.sendError(types.ErrBadSignature, c)
return
}

View File

@ -15,6 +15,7 @@ import (
"github.com/decred/dcrd/txscript/v4/stdaddr"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
@ -30,26 +31,26 @@ func (s *Server) payFee(c *gin.Context) {
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
if s.cfg.VspClosed {
s.sendError(errVspClosed, c)
s.sendError(types.ErrVspClosed, c)
return
}
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(errUnknownTicket, c)
s.sendError(types.ErrUnknownTicket, c)
return
}
var request payFeeRequest
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -59,7 +60,7 @@ func (s *Server) payFee(c *gin.Context) {
ticket.FeeTxStatus == database.FeeConfirmed {
s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeAlreadyReceived, c)
s.sendError(types.ErrFeeAlreadyReceived, c)
return
}
@ -67,7 +68,7 @@ func (s *Server) payFee(c *gin.Context) {
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(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -75,13 +76,13 @@ func (s *Server) payFee(c *gin.Context) {
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.NetParams)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(errTicketCannotVote, c)
s.sendError(types.ErrTicketCannotVote, c)
return
}
@ -89,7 +90,7 @@ func (s *Server) payFee(c *gin.Context) {
if ticket.FeeExpired() {
s.log.Warnf("%s: Expired payfee request (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeExpired, c)
s.sendError(types.ErrFeeExpired, c)
return
}
@ -99,7 +100,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Failed to decode WIF (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(errInvalidPrivKey, c)
s.sendError(types.ErrInvalidPrivKey, c)
return
}
@ -135,7 +136,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Failed to decode fee tx hex (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(errInvalidFeeTx, c)
s.sendError(types.ErrInvalidFeeTx, c)
return
}
@ -143,7 +144,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Fee tx failed sanity check (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendError(errInvalidFeeTx, c)
s.sendError(types.ErrInvalidFeeTx, c)
return
}
@ -152,7 +153,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: Failed to decode fee address (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -174,7 +175,7 @@ func (s *Server) payFee(c *gin.Context) {
funcName, ticket.Hash, ticket.FeeAddress, c.ClientIP())
s.sendErrorWithMsg(
fmt.Sprintf("feetx did not include any payments for fee address %s", ticket.FeeAddress),
errInvalidFeeTx, c)
types.ErrInvalidFeeTx, c)
return
}
@ -183,7 +184,7 @@ func (s *Server) payFee(c *gin.Context) {
if feePaid < minFee {
s.log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
s.sendError(errFeeTooSmall, c)
s.sendError(types.ErrFeeTooSmall, c)
return
}
@ -193,7 +194,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: Failed to get voting address from WIF (ticketHash=%s, clientIP=%s): %v",
funcName, ticket.Hash, c.ClientIP(), err)
s.sendError(errInvalidPrivKey, c)
s.sendError(types.ErrInvalidPrivKey, c)
return
}
@ -204,7 +205,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -217,7 +218,7 @@ func (s *Server) payFee(c *gin.Context) {
s.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",
errInvalidPrivKey, c)
types.ErrInvalidPrivKey, c)
return
}
@ -246,7 +247,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -263,9 +264,9 @@ func (s *Server) payFee(c *gin.Context) {
if strings.Contains(err.Error(),
"references outputs of unknown or fully-spent transaction") {
s.sendError(errCannotBroadcastFeeUnknownOutputs, c)
s.sendError(types.ErrCannotBroadcastFeeUnknownOutputs, c)
} else {
s.sendError(errCannotBroadcastFee, c)
s.sendError(types.ErrCannotBroadcastFee, c)
}
err = s.db.UpdateTicket(ticket)
@ -283,7 +284,7 @@ func (s *Server) payFee(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -292,7 +293,7 @@ func (s *Server) payFee(c *gin.Context) {
}
// Send success response to client.
resp, respSig := s.sendJSONResponse(payFeeResponse{
resp, respSig := s.sendJSONResponse(types.PayFeeResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)

View File

@ -11,6 +11,7 @@ import (
"github.com/decred/dcrd/txscript/v4/stdaddr"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
@ -34,20 +35,20 @@ func (s *Server) setAltSignAddr(c *gin.Context) {
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
reqBytes := c.MustGet(requestBytesKey).([]byte)
if s.cfg.VspClosed {
s.sendError(errVspClosed, c)
s.sendError(types.ErrVspClosed, c)
return
}
var request setAltSignAddrRequest
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -56,13 +57,13 @@ func (s *Server) setAltSignAddr(c *gin.Context) {
currentData, err := s.db.AltSignAddrData(ticketHash)
if err != nil {
s.log.Errorf("%s: db.AltSignAddrData (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.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, errBadRequest, c)
s.sendErrorWithMsg(msg, types.ErrBadRequest, c)
return
}
@ -71,12 +72,12 @@ func (s *Server) setAltSignAddr(c *gin.Context) {
addr, err := stdaddr.DecodeAddressV0(altSignAddr, s.cfg.NetParams)
if err != nil {
s.log.Warnf("%s: Alt sign address cannot be decoded (clientIP=%s): %v", funcName, c.ClientIP(), err)
s.sendErrorWithMsg(err.Error(), errBadRequest, c)
s.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", errBadRequest, c)
s.sendErrorWithMsg("wrong type for alternate signing address", types.ErrBadRequest, c)
return
}
@ -84,7 +85,7 @@ func (s *Server) setAltSignAddr(c *gin.Context) {
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(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -92,18 +93,18 @@ func (s *Server) setAltSignAddr(c *gin.Context) {
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.NetParams)
if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
if !canVote {
s.log.Warnf("%s: unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticketHash)
s.sendError(errTicketCannotVote, c)
s.sendError(types.ErrTicketCannotVote, c)
return
}
// Send success response to client.
resp, respSig := s.sendJSONResponse(setAltSignAddrResponse{
resp, respSig := s.sendJSONResponse(types.SetAltSignAddrResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)

View File

@ -21,6 +21,7 @@ import (
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v3"
"github.com/decred/slog"
"github.com/decred/vspd/database"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
)
@ -211,7 +212,7 @@ func TestSetAltSignAddress(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ticketHash := randString(64, hexCharset)
req := &setAltSignAddrRequest{
req := &types.SetAltSignAddrRequest{
Timestamp: time.Now().Unix(),
TicketHash: ticketHash,
TicketHex: randString(504, hexCharset),

View File

@ -11,6 +11,7 @@ import (
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
@ -28,20 +29,20 @@ 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(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(errUnknownTicket, c)
s.sendError(types.ErrUnknownTicket, c)
return
}
if ticket.FeeTxStatus == database.NoFee {
s.log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeNotReceived, c)
s.sendError(types.ErrFeeNotReceived, c)
return
}
@ -50,14 +51,14 @@ func (s *Server) setVoteChoices(c *gin.Context) {
s.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),
errTicketCannotVote, c)
types.ErrTicketCannotVote, c)
return
}
var request setVoteChoicesRequest
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -67,7 +68,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -79,7 +80,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -87,7 +88,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
s.log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+
"than %d (ticketHash=%s)",
funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash)
s.sendError(errInvalidTimestamp, c)
s.sendError(types.ErrInvalidTimestamp, c)
return
}
}
@ -98,7 +99,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
return
}
@ -106,14 +107,14 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil {
s.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
s.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",
funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c)
s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
}
// Update voting preferences in the database before updating the wallets. DB
@ -135,7 +136,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v",
funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -179,7 +180,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
s.log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash)
// Send success response to client.
resp, respSig := s.sendJSONResponse(setVoteChoicesResponse{
resp, respSig := s.sendJSONResponse(types.SetVoteChoicesResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
}, c)

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/decred/vspd/database"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
@ -23,14 +24,14 @@ func (s *Server) ticketStatus(c *gin.Context) {
if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(errUnknownTicket, c)
s.sendError(types.ErrUnknownTicket, c)
return
}
var request ticketStatusRequest
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(), errBadRequest, c)
s.sendErrorWithMsg(err.Error(), types.ErrBadRequest, c)
return
}
@ -38,7 +39,7 @@ func (s *Server) ticketStatus(c *gin.Context) {
altSignAddrData, err := s.db.AltSignAddrData(ticket.Hash)
if err != nil {
s.log.Errorf("%s: db.AltSignAddrData error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return
}
@ -47,7 +48,7 @@ func (s *Server) ticketStatus(c *gin.Context) {
altSignAddr = altSignAddrData.AltSignAddr
}
s.sendJSONResponse(ticketStatusResponse{
s.sendJSONResponse(types.TicketStatusResponse{
Timestamp: time.Now().Unix(),
Request: reqBytes,
TicketConfirmed: ticket.Confirmed,

View File

@ -7,6 +7,7 @@ package webapi
import (
"time"
"github.com/decred/vspd/types"
"github.com/decred/vspd/version"
"github.com/gin-gonic/gin"
)
@ -14,7 +15,7 @@ import (
// vspInfo is the handler for "GET /api/v3/vspinfo".
func (s *Server) vspInfo(c *gin.Context) {
cachedStats := s.cache.getData()
s.sendJSONResponse(vspInfoResponse{
s.sendJSONResponse(types.VspInfoResponse{
APIVersions: []int64{3},
Timestamp: time.Now().Unix(),
PubKey: s.signPubKey,

View File

@ -21,6 +21,7 @@ import (
"github.com/decred/slog"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/gorilla/sessions"
@ -276,7 +277,7 @@ func (s *Server) sendJSONResponse(resp interface{}, c *gin.Context) (string, str
dec, err := json.Marshal(resp)
if err != nil {
s.log.Errorf("JSON marshal error: %v", err)
s.sendError(errInternalError, c)
s.sendError(types.ErrInternalError, c)
return "", ""
}
@ -289,21 +290,21 @@ func (s *Server) sendJSONResponse(resp interface{}, c *gin.Context) (string, str
return string(dec), sigStr
}
// sendError sends an error response to the client using the default error
// message.
func (s *Server) sendError(e apiError, c *gin.Context) {
msg := e.Error()
// 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) {
msg := e.DefaultMessage()
s.sendErrorWithMsg(msg, e, c)
}
// sendErrorWithMsg sends an error response to the client using the provided
// error message.
func (s *Server) sendErrorWithMsg(msg string, e apiError, c *gin.Context) {
status := e.httpStatus()
// sendErrorWithMsg sends an error response with the provided error code and
// message.
func (s *Server) sendErrorWithMsg(msg string, e types.ErrorCode, c *gin.Context) {
status := e.HTTPStatus()
resp := gin.H{
"code": int(e),
"message": msg,
resp := types.APIError{
Code: int64(e),
Message: msg,
}
// Try to sign the error response. If it fails, send it without a signature.