From d31c24b5311b54a6fba402eb99be16271e066e88 Mon Sep 17 00:00:00 2001 From: Jamie Holdstock Date: Tue, 19 May 2020 17:21:40 +0100 Subject: [PATCH] Add missing tests and log errors. (#36) --- README.md | 6 +++- database/database_test.go | 59 +++++++++++++++++++++++++++++++++++++++ database/ticket.go | 18 ++---------- webapi/methods.go | 12 +++++--- webapi/responses.go | 24 ++++++++-------- 5 files changed, 86 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 79e9cd0..b73689f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ ## 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) - Request fee amount (`GET /fee`) - Request fee address (`POST /feeaddress`) @@ -45,4 +49,4 @@ is used for this project. ## License -dcrvsp is licensed under the [copyfree](http://copyfree.org) ISC License. \ No newline at end of file +dcrvsp is licensed under the [copyfree](http://copyfree.org) ISC License. diff --git a/database/database_test.go b/database/database_test.go index 226d011..4b2edc5 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -22,6 +22,7 @@ func exampleTicket() Ticket { BlockHeight: 2, VoteBits: 3, VotingKey: "VotingKey", + VSPFee: 0.1, Expiration: 4, } } @@ -38,6 +39,8 @@ func TestDatabase(t *testing.T) { "testGetFeesByFeeAddress": testGetFeesByFeeAddress, "testInsertFeeAddressVotingKey": testInsertFeeAddressVotingKey, "testGetInactiveFeeAddresses": testGetInactiveFeeAddresses, + "testUpdateExpireAndFee": testUpdateExpireAndFee, + "testUpdateVoteBits": testUpdateVoteBits, } for testName, test := range tests { @@ -106,6 +109,7 @@ func testGetTicketByHash(t *testing.T) { retrieved.BlockHeight != ticket.BlockHeight || retrieved.VoteBits != ticket.VoteBits || retrieved.VotingKey != ticket.VotingKey || + retrieved.VSPFee != ticket.VSPFee || retrieved.Expiration != ticket.Expiration { t.Fatal("retrieved ticket value didnt match expected") } @@ -217,3 +221,58 @@ func testGetInactiveFeeAddresses(t *testing.T) { 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") + } +} diff --git a/database/ticket.go b/database/ticket.go index c1578f1..4d72c2d 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -150,7 +150,7 @@ func (vdb *VspDatabase) GetTicketByHash(hash string) (Ticket, 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) 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) } - // 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) }) } 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) 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) } - // 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) }) } diff --git a/webapi/methods.go b/webapi/methods.go index f2e5310..54b3973 100644 --- a/webapi/methods.go +++ b/webapi/methods.go @@ -89,7 +89,8 @@ func feeAddress(c *gin.Context) { // Check for existing response ticket, err := db.GetTicketByHash(ticketHashStr) 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 } if err == nil { @@ -104,7 +105,8 @@ func feeAddress(c *gin.Context) { err = db.UpdateExpireAndFee(ticketHashStr, expire, VSPFee) if err != nil { - c.AbortWithError(http.StatusInternalServerError, errors.New("database error")) + log.Errorf("UpdateExpireAndFee error: %v", err) + sendErrorResponse("database error", http.StatusInternalServerError, c) return } } @@ -118,7 +120,8 @@ func feeAddress(c *gin.Context) { return } - c.AbortWithError(http.StatusBadRequest, errors.New("invalid signature")) + log.Warnf("Invalid signature from %s", c.ClientIP()) + sendErrorResponse("invalid signature", http.StatusBadRequest, c) return } @@ -473,7 +476,8 @@ func setVoteBits(c *gin.Context) { err = db.UpdateVoteBits(txHash.String(), voteBits) if err != nil { - c.AbortWithError(http.StatusInternalServerError, errors.New("database error")) + log.Errorf("UpdateVoteBits error: %v", err) + sendErrorResponse("database error", http.StatusInternalServerError, c) return } diff --git a/webapi/responses.go b/webapi/responses.go index af158ba..0e2517b 100644 --- a/webapi/responses.go +++ b/webapi/responses.go @@ -2,7 +2,7 @@ package webapi type pubKeyResponse struct { Timestamp int64 `json:"timestamp" binding:"required"` - PubKey []byte `json:"pubKey" binding:"required"` + PubKey []byte `json:"pubkey" binding:"required"` } type feeResponse struct { @@ -12,13 +12,13 @@ type feeResponse struct { type FeeAddressRequest struct { Timestamp int64 `json:"timestamp" binding:"required"` - TicketHash string `json:"ticketHash" binding:"required"` + TicketHash string `json:"tickethash" binding:"required"` Signature string `json:"signature" binding:"required"` } type feeAddressResponse struct { Timestamp int64 `json:"timestamp" binding:"required"` - FeeAddress string `json:"feeAddress" binding:"required"` + FeeAddress string `json:"feeaddress" binding:"required"` Fee float64 `json:"fee" binding:"required"` Expiration int64 `json:"expiration" binding:"required"` Request FeeAddressRequest `json:"request" binding:"required"` @@ -26,33 +26,33 @@ type feeAddressResponse struct { type PayFeeRequest struct { Timestamp int64 `json:"timestamp" binding:"required"` - Hex string `json:"feeTx" binding:"required"` - VotingKey string `json:"votingKey" binding:"required"` - VoteBits uint16 `json:"voteBits" binding:"required"` + Hex string `json:"feetx" binding:"required"` + VotingKey string `json:"votingkey" binding:"required"` + VoteBits uint16 `json:"votebits" binding:"required"` } type payFeeResponse struct { Timestamp int64 `json:"timestamp" binding:"required"` - TxHash string `json:"txHash" binding:"required"` + TxHash string `json:"txhash" binding:"required"` Request PayFeeRequest `json:"request" binding:"required"` } type SetVoteBitsRequest struct { Timestamp int64 `json:"timestamp" binding:"required"` - TicketHash string `json:"ticketHash" binding:"required"` - Signature string `json:"commitmentSignature" binding:"required"` - VoteBits uint16 `json:"voteBits" binding:"required"` + TicketHash string `json:"tickethash" binding:"required"` + Signature string `json:"commitmentsignature" binding:"required"` + VoteBits uint16 `json:"votebits" binding:"required"` } type setVoteBitsResponse struct { Timestamp int64 `json:"timestamp" binding:"required"` Request SetVoteBitsRequest `json:"request" binding:"required"` - VoteBits uint16 `json:"voteBits" binding:"required"` + VoteBits uint16 `json:"votebits" binding:"required"` } type TicketStatusRequest struct { Timestamp int64 `json:"timestamp" binding:"required"` - TicketHash string `json:"ticketHash" binding:"required"` + TicketHash string `json:"tickethash" binding:"required"` Signature string `json:"signature" binding:"required"` }