Add missing tests and log errors. (#36)
This commit is contained in:
parent
f50b0aba56
commit
d31c24b531
@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
## MVP features
|
## MVP features
|
||||||
|
|
||||||
|
- When dcrvsp is started for the first time, it generates a ed25519 keypair and
|
||||||
|
stores it in the database. This key is used to sign all API responses, and the
|
||||||
|
signature is included in the response header `VSP-Signature`. Error responses
|
||||||
|
are not signed.
|
||||||
- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
|
- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
|
||||||
- Request fee amount (`GET /fee`)
|
- Request fee amount (`GET /fee`)
|
||||||
- Request fee address (`POST /feeaddress`)
|
- Request fee address (`POST /feeaddress`)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ func exampleTicket() Ticket {
|
|||||||
BlockHeight: 2,
|
BlockHeight: 2,
|
||||||
VoteBits: 3,
|
VoteBits: 3,
|
||||||
VotingKey: "VotingKey",
|
VotingKey: "VotingKey",
|
||||||
|
VSPFee: 0.1,
|
||||||
Expiration: 4,
|
Expiration: 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,6 +39,8 @@ func TestDatabase(t *testing.T) {
|
|||||||
"testGetFeesByFeeAddress": testGetFeesByFeeAddress,
|
"testGetFeesByFeeAddress": testGetFeesByFeeAddress,
|
||||||
"testInsertFeeAddressVotingKey": testInsertFeeAddressVotingKey,
|
"testInsertFeeAddressVotingKey": testInsertFeeAddressVotingKey,
|
||||||
"testGetInactiveFeeAddresses": testGetInactiveFeeAddresses,
|
"testGetInactiveFeeAddresses": testGetInactiveFeeAddresses,
|
||||||
|
"testUpdateExpireAndFee": testUpdateExpireAndFee,
|
||||||
|
"testUpdateVoteBits": testUpdateVoteBits,
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, test := range tests {
|
for testName, test := range tests {
|
||||||
@ -106,6 +109,7 @@ func testGetTicketByHash(t *testing.T) {
|
|||||||
retrieved.BlockHeight != ticket.BlockHeight ||
|
retrieved.BlockHeight != ticket.BlockHeight ||
|
||||||
retrieved.VoteBits != ticket.VoteBits ||
|
retrieved.VoteBits != ticket.VoteBits ||
|
||||||
retrieved.VotingKey != ticket.VotingKey ||
|
retrieved.VotingKey != ticket.VotingKey ||
|
||||||
|
retrieved.VSPFee != ticket.VSPFee ||
|
||||||
retrieved.Expiration != ticket.Expiration {
|
retrieved.Expiration != ticket.Expiration {
|
||||||
t.Fatal("retrieved ticket value didnt match expected")
|
t.Fatal("retrieved ticket value didnt match expected")
|
||||||
}
|
}
|
||||||
@ -217,3 +221,58 @@ func testGetInactiveFeeAddresses(t *testing.T) {
|
|||||||
t.Fatal("fee address didnt match expected")
|
t.Fatal("fee address didnt match expected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testUpdateExpireAndFee(t *testing.T) {
|
||||||
|
// Insert a ticket into the database.
|
||||||
|
ticket := exampleTicket()
|
||||||
|
err := db.InsertFeeAddress(ticket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error storing ticket in database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ticket with new values.
|
||||||
|
newExpiry := ticket.Expiration + 1
|
||||||
|
newFee := ticket.VSPFee + 1
|
||||||
|
err = db.UpdateExpireAndFee(ticket.Hash, newExpiry, newFee)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error updating expiry and fee: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get updated ticket
|
||||||
|
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error retrieving updated ticket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ticket fields match expected.
|
||||||
|
if retrieved.VSPFee != newFee || retrieved.Expiration != newExpiry {
|
||||||
|
t.Fatal("retrieved ticket value didnt match expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateVoteBits(t *testing.T) {
|
||||||
|
// Insert a ticket into the database.
|
||||||
|
ticket := exampleTicket()
|
||||||
|
err := db.InsertFeeAddress(ticket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error storing ticket in database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ticket with new votebits.
|
||||||
|
newVoteBits := ticket.VoteBits + 1
|
||||||
|
err = db.UpdateVoteBits(ticket.Hash, newVoteBits)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error updating votebits: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get updated ticket
|
||||||
|
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error retrieving updated ticket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ticket fields match expected.
|
||||||
|
if retrieved.VoteBits != newVoteBits {
|
||||||
|
t.Fatal("retrieved ticket value didnt match expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -150,7 +150,7 @@ func (vdb *VspDatabase) GetTicketByHash(hash string) (Ticket, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vdb *VspDatabase) UpdateVoteBits(hash string, voteBits uint16) error {
|
func (vdb *VspDatabase) UpdateVoteBits(hash string, voteBits uint16) error {
|
||||||
return vdb.db.View(func(tx *bolt.Tx) error {
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||||
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
||||||
key := []byte(hash)
|
key := []byte(hash)
|
||||||
|
|
||||||
@ -171,19 +171,12 @@ func (vdb *VspDatabase) UpdateVoteBits(hash string, voteBits uint16) error {
|
|||||||
return fmt.Errorf("could not marshal ticket: %v", err)
|
return fmt.Errorf("could not marshal ticket: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete existing ticket
|
|
||||||
err = ticketBkt.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete ticket: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add updated ticket
|
|
||||||
return ticketBkt.Put(key, ticketBytes)
|
return ticketBkt.Put(key, ticketBytes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vdb *VspDatabase) UpdateExpireAndFee(hash string, expiration int64, vspFee float64) error {
|
func (vdb *VspDatabase) UpdateExpireAndFee(hash string, expiration int64, vspFee float64) error {
|
||||||
return vdb.db.View(func(tx *bolt.Tx) error {
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||||
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
||||||
key := []byte(hash)
|
key := []byte(hash)
|
||||||
|
|
||||||
@ -205,13 +198,6 @@ func (vdb *VspDatabase) UpdateExpireAndFee(hash string, expiration int64, vspFee
|
|||||||
return fmt.Errorf("could not marshal ticket: %v", err)
|
return fmt.Errorf("could not marshal ticket: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete existing ticket
|
|
||||||
err = ticketBkt.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete ticket: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add updated ticket
|
|
||||||
return ticketBkt.Put(key, ticketBytes)
|
return ticketBkt.Put(key, ticketBytes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,7 +89,8 @@ func feeAddress(c *gin.Context) {
|
|||||||
// Check for existing response
|
// Check for existing response
|
||||||
ticket, err := db.GetTicketByHash(ticketHashStr)
|
ticket, err := db.GetTicketByHash(ticketHashStr)
|
||||||
if err != nil && !errors.Is(err, database.ErrNoTicketFound) {
|
if err != nil && !errors.Is(err, database.ErrNoTicketFound) {
|
||||||
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
|
log.Errorf("GetTicketByHash error: %v", err)
|
||||||
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -104,7 +105,8 @@ func feeAddress(c *gin.Context) {
|
|||||||
|
|
||||||
err = db.UpdateExpireAndFee(ticketHashStr, expire, VSPFee)
|
err = db.UpdateExpireAndFee(ticketHashStr, expire, VSPFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
|
log.Errorf("UpdateExpireAndFee error: %v", err)
|
||||||
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +120,8 @@ func feeAddress(c *gin.Context) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.AbortWithError(http.StatusBadRequest, errors.New("invalid signature"))
|
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||||
|
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,7 +476,8 @@ func setVoteBits(c *gin.Context) {
|
|||||||
|
|
||||||
err = db.UpdateVoteBits(txHash.String(), voteBits)
|
err = db.UpdateVoteBits(txHash.String(), voteBits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
|
log.Errorf("UpdateVoteBits error: %v", err)
|
||||||
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package webapi
|
|||||||
|
|
||||||
type pubKeyResponse struct {
|
type pubKeyResponse struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
PubKey []byte `json:"pubKey" binding:"required"`
|
PubKey []byte `json:"pubkey" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type feeResponse struct {
|
type feeResponse struct {
|
||||||
@ -12,13 +12,13 @@ type feeResponse struct {
|
|||||||
|
|
||||||
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"`
|
||||||
Signature string `json:"signature" binding:"required"`
|
Signature string `json:"signature" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type feeAddressResponse struct {
|
type feeAddressResponse struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
FeeAddress string `json:"feeAddress" binding:"required"`
|
FeeAddress string `json:"feeaddress" binding:"required"`
|
||||||
Fee float64 `json:"fee" binding:"required"`
|
Fee float64 `json:"fee" binding:"required"`
|
||||||
Expiration int64 `json:"expiration" binding:"required"`
|
Expiration int64 `json:"expiration" binding:"required"`
|
||||||
Request FeeAddressRequest `json:"request" binding:"required"`
|
Request FeeAddressRequest `json:"request" binding:"required"`
|
||||||
@ -26,33 +26,33 @@ type feeAddressResponse struct {
|
|||||||
|
|
||||||
type PayFeeRequest struct {
|
type PayFeeRequest struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
Hex string `json:"feeTx" binding:"required"`
|
Hex string `json:"feetx" binding:"required"`
|
||||||
VotingKey string `json:"votingKey" binding:"required"`
|
VotingKey string `json:"votingkey" binding:"required"`
|
||||||
VoteBits uint16 `json:"voteBits" binding:"required"`
|
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type payFeeResponse struct {
|
type payFeeResponse struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
TxHash string `json:"txHash" binding:"required"`
|
TxHash string `json:"txhash" binding:"required"`
|
||||||
Request PayFeeRequest `json:"request" binding:"required"`
|
Request PayFeeRequest `json:"request" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetVoteBitsRequest struct {
|
type SetVoteBitsRequest 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"`
|
||||||
Signature string `json:"commitmentSignature" binding:"required"`
|
Signature string `json:"commitmentsignature" binding:"required"`
|
||||||
VoteBits uint16 `json:"voteBits" binding:"required"`
|
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type setVoteBitsResponse struct {
|
type setVoteBitsResponse struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
Request SetVoteBitsRequest `json:"request" binding:"required"`
|
Request SetVoteBitsRequest `json:"request" binding:"required"`
|
||||||
VoteBits uint16 `json:"voteBits" binding:"required"`
|
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketStatusRequest struct {
|
type TicketStatusRequest 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"`
|
||||||
Signature string `json:"signature" binding:"required"`
|
Signature string `json:"signature" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user