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

View File

@ -1,18 +1,39 @@
#!/bin/bash #!/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 # Use of this source code is governed by an ISC
# license that can be found in the LICENSE file. # license that can be found in the LICENSE file.
# #
# usage: # usage:
# ./run_tests.sh # ./run_tests.sh
set -ex set -e
go version go version
# run tests # run tests on all modules
env GORACE="halt_on_error=1" go test -race ./... 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}/...
# 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 # run linter
golangci-lint run 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 // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // 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"` APIVersions []int64 `json:"apiversions"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
PubKey []byte `json:"pubkey"` PubKey []byte `json:"pubkey"`
@ -22,14 +29,14 @@ type vspInfoResponse struct {
NetworkProportion float32 `json:"estimatednetworkproportion"` NetworkProportion float32 `json:"estimatednetworkproportion"`
} }
type feeAddressRequest struct { type FeeAddressRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"` Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"` TicketHash string `json:"tickethash" binding:"required"`
TicketHex string `json:"tickethex" binding:"required"` TicketHex string `json:"tickethex" binding:"required"`
ParentHex string `json:"parenthex" binding:"required"` ParentHex string `json:"parenthex" binding:"required"`
} }
type feeAddressResponse struct { type FeeAddressResponse struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
FeeAddress string `json:"feeaddress"` FeeAddress string `json:"feeaddress"`
FeeAmount int64 `json:"feeamount"` FeeAmount int64 `json:"feeamount"`
@ -37,7 +44,7 @@ type feeAddressResponse struct {
Request []byte `json:"request"` Request []byte `json:"request"`
} }
type payFeeRequest struct { type PayFeeRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"` Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"` TicketHash string `json:"tickethash" binding:"required"`
FeeTx string `json:"feetx" binding:"required"` FeeTx string `json:"feetx" binding:"required"`
@ -47,12 +54,12 @@ type payFeeRequest struct {
TreasuryPolicy map[string]string `json:"treasurypolicy" binding:"max=3"` TreasuryPolicy map[string]string `json:"treasurypolicy" binding:"max=3"`
} }
type payFeeResponse struct { type PayFeeResponse struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"` Request []byte `json:"request"`
} }
type setVoteChoicesRequest struct { type SetVoteChoicesRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"` Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"` TicketHash string `json:"tickethash" binding:"required"`
VoteChoices map[string]string `json:"votechoices" 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"` TreasuryPolicy map[string]string `json:"treasurypolicy" binding:"max=3"`
} }
type setVoteChoicesResponse struct { type SetVoteChoicesResponse struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"` Request []byte `json:"request"`
} }
type ticketStatusRequest struct { type TicketStatusRequest struct {
TicketHash string `json:"tickethash" binding:"required"` TicketHash string `json:"tickethash" binding:"required"`
} }
type ticketStatusResponse struct { type TicketStatusResponse struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
TicketConfirmed bool `json:"ticketconfirmed"` TicketConfirmed bool `json:"ticketconfirmed"`
FeeTxStatus string `json:"feetxstatus"` FeeTxStatus string `json:"feetxstatus"`
@ -81,7 +88,7 @@ type ticketStatusResponse struct {
Request []byte `json:"request"` Request []byte `json:"request"`
} }
type setAltSignAddrRequest struct { type SetAltSignAddrRequest struct {
Timestamp int64 `json:"timestamp" binding:"required"` Timestamp int64 `json:"timestamp" binding:"required"`
TicketHash string `json:"tickethash" binding:"required"` TicketHash string `json:"tickethash" binding:"required"`
TicketHex string `json:"tickethex" binding:"required"` TicketHex string `json:"tickethex" binding:"required"`
@ -89,7 +96,7 @@ type setAltSignAddrRequest struct {
AltSignAddress string `json:"altsignaddress" binding:"required"` AltSignAddress string `json:"altsignaddress" binding:"required"`
} }
type setAltSignAddrResponse struct { type SetAltSignAddrResponse struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Request []byte `json:"request"` 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/dcrd/dcrutil/v4"
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
) )
@ -84,20 +85,20 @@ func (s *Server) feeAddress(c *gin.Context) {
dcrdErr := c.MustGet(dcrdErrorKey) dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil { if dcrdErr != nil {
s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error)) s.log.Errorf("%s: Could not get dcrd client: %v", funcName, dcrdErr.(error))
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
reqBytes := c.MustGet(requestBytesKey).([]byte) reqBytes := c.MustGet(requestBytesKey).([]byte)
if s.cfg.VspClosed { if s.cfg.VspClosed {
s.sendError(errVspClosed, c) s.sendError(types.ErrVspClosed, c)
return return
} }
var request feeAddressRequest var request types.FeeAddressRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) 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 return
} }
@ -110,7 +111,7 @@ func (s *Server) feeAddress(c *gin.Context) {
ticket.FeeTxStatus == database.FeeConfirmed) { ticket.FeeTxStatus == database.FeeConfirmed) {
s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)", s.log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash) funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeAlreadyReceived, c) s.sendError(types.ErrFeeAlreadyReceived, c)
return return
} }
@ -118,7 +119,7 @@ func (s *Server) feeAddress(c *gin.Context) {
rawTicket, err := dcrdClient.GetRawTransaction(ticketHash) rawTicket, err := dcrdClient.GetRawTransaction(ticketHash)
if err != nil { if err != nil {
s.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticketHash, err) 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 return
} }
@ -126,13 +127,13 @@ func (s *Server) feeAddress(c *gin.Context) {
canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.NetParams) canVote, err := canTicketVote(rawTicket, dcrdClient, s.cfg.NetParams)
if err != nil { if err != nil {
s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err) s.log.Errorf("%s: canTicketVote error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
if !canVote { if !canVote {
s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)", s.log.Warnf("%s: Unvotable ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticketHash) funcName, c.ClientIP(), ticketHash)
s.sendError(errTicketCannotVote, c) s.sendError(types.ErrTicketCannotVote, c)
return return
} }
@ -145,7 +146,7 @@ func (s *Server) feeAddress(c *gin.Context) {
newFee, err := s.getCurrentFee(dcrdClient) newFee, err := s.getCurrentFee(dcrdClient)
if err != nil { if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err) s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticket.Hash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
ticket.FeeExpiration = now.Add(feeAddressExpiration).Unix() ticket.FeeExpiration = now.Add(feeAddressExpiration).Unix()
@ -155,13 +156,13 @@ func (s *Server) feeAddress(c *gin.Context) {
if err != nil { if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v", s.log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
funcName, ticket.Hash, err) funcName, ticket.Hash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
s.log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)", s.log.Debugf("%s: Expired fee updated (newFeeAmt=%s, ticketHash=%s)",
funcName, newFee, ticket.Hash) funcName, newFee, ticket.Hash)
} }
s.sendJSONResponse(feeAddressResponse{ s.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(), Timestamp: now.Unix(),
Request: reqBytes, Request: reqBytes,
FeeAddress: ticket.FeeAddress, FeeAddress: ticket.FeeAddress,
@ -178,14 +179,14 @@ func (s *Server) feeAddress(c *gin.Context) {
fee, err := s.getCurrentFee(dcrdClient) fee, err := s.getCurrentFee(dcrdClient)
if err != nil { if err != nil {
s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err) s.log.Errorf("%s: getCurrentFee error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
newAddress, newAddressIdx, err := s.getNewFeeAddress() newAddress, newAddressIdx, err := s.getNewFeeAddress()
if err != nil { if err != nil {
s.log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err) s.log.Errorf("%s: getNewFeeAddress error (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
@ -216,7 +217,7 @@ func (s *Server) feeAddress(c *gin.Context) {
err = s.db.InsertNewTicket(dbTicket) err = s.db.InsertNewTicket(dbTicket)
if err != nil { if err != nil {
s.log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err) s.log.Errorf("%s: db.InsertNewTicket failed (ticketHash=%s): %v", funcName, ticketHash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
@ -224,7 +225,7 @@ func (s *Server) feeAddress(c *gin.Context) {
"feeAddr=%s, feeAmt=%s, ticketHash=%s)", "feeAddr=%s, feeAmt=%s, ticketHash=%s)",
funcName, confirmed, newAddressIdx, newAddress, fee, ticketHash) funcName, confirmed, newAddressIdx, newAddress, fee, ticketHash)
s.sendJSONResponse(feeAddressResponse{ s.sendJSONResponse(types.FeeAddressResponse{
Timestamp: now.Unix(), Timestamp: now.Unix(),
Request: reqBytes, Request: reqBytes,
FeeAddress: newAddress, FeeAddress: newAddress,

View File

@ -17,7 +17,6 @@ import (
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v3" dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v3"
"github.com/decred/dcrd/wire" "github.com/decred/dcrd/wire"
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
) )
func currentVoteVersion(params *chaincfg.Params) uint32 { func currentVoteVersion(params *chaincfg.Params) uint32 {
@ -170,32 +169,6 @@ func validateTicketHash(hash string) error {
return nil 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 // 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. // 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) { func canTicketVote(rawTx *dcrdtypes.TxRawResult, dcrdClient Node, netParams *chaincfg.Params) (bool, error) {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import (
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "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 // If we cannot set the vote choices on at least one voting wallet right
// now, don't update the database, just return an error. // now, don't update the database, just return an error.
if len(walletClients) == 0 { if len(walletClients) == 0 {
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
if !knownTicket { if !knownTicket {
s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP()) s.log.Warnf("%s: Unknown ticket (clientIP=%s)", funcName, c.ClientIP())
s.sendError(errUnknownTicket, c) s.sendError(types.ErrUnknownTicket, c)
return return
} }
if ticket.FeeTxStatus == database.NoFee { if ticket.FeeTxStatus == database.NoFee {
s.log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)", s.log.Warnf("%s: No fee tx for ticket (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash) funcName, c.ClientIP(), ticket.Hash)
s.sendError(errFeeNotReceived, c) s.sendError(types.ErrFeeNotReceived, c)
return 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)", s.log.Warnf("%s: Ticket not eligible to vote (clientIP=%s, ticketHash=%s)",
funcName, c.ClientIP(), ticket.Hash) funcName, c.ClientIP(), ticket.Hash)
s.sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome), s.sendErrorWithMsg(fmt.Sprintf("ticket not eligible to vote (status=%s)", ticket.Outcome),
errTicketCannotVote, c) types.ErrTicketCannotVote, c)
return return
} }
var request setVoteChoicesRequest var request types.SetVoteChoicesRequest
if err := binding.JSON.BindBody(reqBytes, &request); err != nil { if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
s.log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err) 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 return
} }
@ -67,7 +68,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil { if err != nil {
s.log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v", s.log.Errorf("%s: db.GetVoteChanges error (ticketHash=%s): %v",
funcName, ticket.Hash, err) funcName, ticket.Hash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
@ -79,7 +80,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil { if err != nil {
s.log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v", s.log.Errorf("%s: Could not unmarshal vote change record (ticketHash=%s): %v",
funcName, ticket.Hash, err) funcName, ticket.Hash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
@ -87,7 +88,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
s.log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+ s.log.Warnf("%s: Request uses invalid timestamp, %d is not greater "+
"than %d (ticketHash=%s)", "than %d (ticketHash=%s)",
funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash) funcName, request.Timestamp, prevReq.Timestamp, ticket.Hash)
s.sendError(errInvalidTimestamp, c) s.sendError(types.ErrInvalidTimestamp, c)
return return
} }
} }
@ -98,7 +99,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil { if err != nil {
s.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v", s.log.Warnf("%s: Invalid consensus vote choices (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err) funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c) s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
return return
} }
@ -106,14 +107,14 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil { if err != nil {
s.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v", s.log.Warnf("%s: Invalid treasury policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err) funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c) s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
} }
err = validTSpendPolicy(request.TSpendPolicy) err = validTSpendPolicy(request.TSpendPolicy)
if err != nil { if err != nil {
s.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v", s.log.Warnf("%s: Invalid tspend policy (clientIP=%s, ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err) funcName, c.ClientIP(), ticket.Hash, err)
s.sendErrorWithMsg(err.Error(), errInvalidVoteChoices, c) s.sendErrorWithMsg(err.Error(), types.ErrInvalidVoteChoices, c)
} }
// Update voting preferences in the database before updating the wallets. DB // Update voting preferences in the database before updating the wallets. DB
@ -135,7 +136,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
if err != nil { if err != nil {
s.log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v", s.log.Errorf("%s: db.UpdateTicket error, failed to set consensus vote choices (ticketHash=%s): %v",
funcName, ticket.Hash, err) funcName, ticket.Hash, err)
s.sendError(errInternalError, c) s.sendError(types.ErrInternalError, c)
return return
} }
@ -179,7 +180,7 @@ func (s *Server) setVoteChoices(c *gin.Context) {
s.log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash) s.log.Debugf("%s: Vote choices updated (ticketHash=%s)", funcName, ticket.Hash)
// Send success response to client. // Send success response to client.
resp, respSig := s.sendJSONResponse(setVoteChoicesResponse{ resp, respSig := s.sendJSONResponse(types.SetVoteChoicesResponse{
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
Request: reqBytes, Request: reqBytes,
}, c) }, c)

View File

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

View File

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

View File

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