Add missing tests and log errors. (#36)

This commit is contained in:
Jamie Holdstock 2020-05-19 17:21:40 +01:00 committed by GitHub
parent f50b0aba56
commit d31c24b531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 33 deletions

View File

@ -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`)
@ -45,4 +49,4 @@ is used for this project.
## License ## License
dcrvsp is licensed under the [copyfree](http://copyfree.org) ISC License. dcrvsp is licensed under the [copyfree](http://copyfree.org) ISC License.

View File

@ -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")
}
}

View File

@ -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)
}) })
} }

View File

@ -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
} }

View File

@ -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"`
} }